Skip to content

Commit

Permalink
Merge pull request #2137 from subspace/revamp_proof_generation
Browse files Browse the repository at this point in the history
Use storage proof to provide proof of inclusion for extrinsics
  • Loading branch information
vedhavyas authored Oct 19, 2023
2 parents 77eaf11 + 8c59838 commit 2ab41c9
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 96 deletions.
2 changes: 1 addition & 1 deletion crates/sp-domains-fraud-proof/src/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<DomainHeader::Hashing>::verify_and_get_value::<Digest>(
let digest = StorageProofVerifier::<DomainHeader::Hashing>::get_decoded_value::<Digest>(
&state_root,
digest_storage_proof,
digest_storage_key,
Expand Down
17 changes: 11 additions & 6 deletions crates/sp-domains/src/fraud_proof.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;

Expand All @@ -30,7 +31,7 @@ pub enum ExecutionPhase {
InitializeBlock,
/// Executes some extrinsic.
ApplyExtrinsic {
proof_of_inclusion: Vec<Vec<u8>>,
proof_of_inclusion: StorageProof,
mismatch_index: u32,
extrinsic: Vec<u8>,
},
Expand Down Expand Up @@ -155,11 +156,15 @@ impl ExecutionPhase {
mismatch_index,
extrinsic,
} => {
if !valued_trie_root::verify_proof::<LayoutV1<DomainHeader::Hashing>>(
proof_of_inclusion,
let storage_key =
StorageProofVerifier::<DomainHeader::Hashing>::enumerated_storage_key(
*mismatch_index,
);
if !StorageProofVerifier::<DomainHeader::Hashing>::verify_storage_proof(
proof_of_inclusion.clone(),
&bad_receipt.domain_block_extrinsic_root.into(),
extrinsic.clone(),
*mismatch_index,
storage_key,
) {
return Err(VerificationError::InvalidApplyExtrinsicCallData);
}
Expand Down
136 changes: 79 additions & 57 deletions crates/sp-domains/src/valued_trie_root.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -251,59 +256,58 @@ type MemoryDB<T> = 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<Layout: TrieLayout>(input: &[Vec<u8>], index: u32) -> Option<Vec<Vec<u8>>> {
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<Layout>(PhantomData<Layout>);

let (db, root) = {
let mut db = <MemoryDB<Layout>>::default();
let mut root = Default::default();
{
let mut trie = <TrieDBMutBuilder<Layout>>::new(&mut db, &mut root).build();
for (key, value) in input.iter() {
trie.insert(key, value).ok()?;
}
impl<Layout> StorageProofProvider<Layout>
where
Layout: TrieLayout,
<Layout::Hash as Hasher>::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<u8>],
index: u32,
) -> Option<StorageProof> {
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 = <MemoryDB<Layout>>::default();
let mut root = Default::default();
{
let mut trie = <TrieDBMutBuilder<Layout>>::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<Layout>(
proof: &[Vec<u8>],
root: &<<Layout as TrieLayout>::Hash as Hasher>::Out,
data: Vec<u8>,
index: u32,
) -> bool
where
Layout: TrieLayout,
{
let key = Compact(index).encode();
trie_db::proof::verify_proof::<Layout, _, _, _>(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;
Expand Down Expand Up @@ -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::<LayoutV1<BlakeTwo256>>(&exts, i as u32).unwrap();
assert!(verify_proof::<LayoutV1<BlakeTwo256>>(
&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::<LayoutV1<BlakeTwo256>>::generate_enumerated_proof_of_inclusion(
&exts,
i as u32,
)
.unwrap();

assert!(StorageProofVerifier::<BlakeTwo256>::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::<LayoutV1<BlakeTwo256>>(
&proof,
assert!(!StorageProofVerifier::<BlakeTwo256>::verify_storage_proof(
storage_proof.clone(),
&H256::random(),
ext.clone(),
i as u32,
storage_key.clone(),
));
assert!(!verify_proof::<LayoutV1<BlakeTwo256>>(
&proof,

assert!(!StorageProofVerifier::<BlakeTwo256>::verify_storage_proof(
storage_proof.clone(),
&root,
vec![i as u8; ext.len()],
i as u32,
storage_key,
));
assert!(!verify_proof::<LayoutV1<BlakeTwo256>>(
&proof,

let storage_key = StorageKey(Compact(i as u32 + 1).encode());
assert!(!StorageProofVerifier::<BlakeTwo256>::verify_storage_proof(
storage_proof,
&root,
ext,
i as u32 + 1,
storage_key,
));
}

// fails to generate storage key for unknown index
assert!(
StorageProofProvider::<LayoutV1<BlakeTwo256>>::generate_enumerated_proof_of_inclusion(
&exts, 100,
)
.is_none()
);
}
}
44 changes: 39 additions & 5 deletions crates/sp-domains/src/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -31,22 +31,56 @@ pub enum VerificationError {
InvalidBundleDigest,
}

/// Type that provides utilities to verify the storage proof.
pub struct StorageProofVerifier<H: Hasher>(PhantomData<H>);

impl<H: Hasher> StorageProofVerifier<H> {
pub fn verify_and_get_value<V: Decode>(
/// Extracts the value against a given key and returns a decoded value.
pub fn get_decoded_value<V: Decode>(
state_root: &H::Out,
proof: StorageProof,
key: StorageKey,
) -> Result<V, VerificationError> {
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<Vec<u8>, VerificationError> {
let db = proof.into_memory_db::<H>();
let val = read_trie_value::<LayoutV1<H>, _>(&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<u8>,
storage_key: StorageKey,
) -> bool
where
H: Hasher,
{
if let Ok(got_data) = StorageProofVerifier::<H>::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())
}
}

Expand Down Expand Up @@ -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::<Hashing>::verify_and_get_value::<Balance>(
let total_rewards = StorageProofVerifier::<Hashing>::get_decoded_value::<Balance>(
&state_root,
storage_proof,
storage_key,
Expand Down
15 changes: 7 additions & 8 deletions crates/subspace-fraud-proof/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<BlakeTwo256>,
>(
>::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,
Expand Down Expand Up @@ -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<BlakeTwo256>,
>(
>::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,
Expand Down
10 changes: 6 additions & 4 deletions domains/client/domain-operator/src/fraud_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -385,10 +386,11 @@ where
)?;

let execution_phase = {
let proof_of_inclusion = sp_domains::valued_trie_root::generate_proof::<
LayoutV1<BlakeTwo256>,
>(
encoded_extrinsics.as_slice(), extrinsic_index as u32
let proof_of_inclusion = StorageProofProvider::<
LayoutV1<<Block::Header as HeaderT>::Hashing>,
>::generate_enumerated_proof_of_inclusion(
encoded_extrinsics.as_slice(),
extrinsic_index as u32,
)
.ok_or(FraudProofError::FailToGenerateProofOfInclusion)?;
ExecutionPhase::ApplyExtrinsic {
Expand Down
Loading

0 comments on commit 2ab41c9

Please sign in to comment.