diff --git a/Cargo.lock b/Cargo.lock index 4994a89e3d..258fca0c15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2397,6 +2397,7 @@ dependencies = [ "pallet-collator-selection", "pallet-did-lookup", "pallet-dip-consumer", + "pallet-postit", "pallet-relay-store", "pallet-session", "pallet-sudo", @@ -6772,6 +6773,18 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-postit" +version = "1.12.0-dev" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-preimage" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 1b952718fd..cf103d157b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ version = "1.12.0-dev" members = [ "crates/*", "dip-template/nodes/*", + "dip-template/pallets/*", "dip-template/runtimes/*", "nodes/*", "pallets/*", @@ -42,7 +43,7 @@ jsonrpsee = "0.16.2" libsecp256k1 = {version = "0.7", default-features = false} log = "0.4.17" parity-scale-codec = {version = "3.1.5", default-features = false} -scale-info = {version = "2.1.1", default-features = false} +scale-info = {version = "2.7.0", default-features = false} serde = "1.0.144" serde_json = "1.0.85" sha3 = {version = "0.10.0", default-features = false} @@ -72,6 +73,7 @@ runtime-common = {path = "runtimes/common", default-features = false} # Templates dip-consumer-runtime-template = {path = "dip-template/runtimes/dip-consumer", default-features = false} dip-provider-runtime-template = {path = "dip-template/runtimes/dip-provider", default-features = false} +pallet-postit = {path = "dip-template/pallets/pallet-postit", default-features = false} # Internal runtime API (with default disabled) kilt-runtime-api-did = {path = "runtime-api/did", default-features = false} diff --git a/crates/kilt-dip-support/src/lib.rs b/crates/kilt-dip-support/src/lib.rs index 9e1819ed7d..b19daada83 100644 --- a/crates/kilt-dip-support/src/lib.rs +++ b/crates/kilt-dip-support/src/lib.rs @@ -20,10 +20,13 @@ #![cfg_attr(not(feature = "std"), no_std)] -use parity_scale_codec::{Decode, Encode, HasCompact}; +use parity_scale_codec::{Codec, Decode, Encode, HasCompact}; use scale_info::TypeInfo; use sp_core::{Get, RuntimeDebug, U256}; -use sp_runtime::traits::CheckedSub; +use sp_runtime::{ + generic::Header, + traits::{AtLeast32BitUnsigned, CheckedSub, Hash, MaybeDisplay, Member, SimpleBitOps}, +}; use sp_std::{borrow::Borrow, marker::PhantomData, vec::Vec}; use ::did::{did_details::DidVerificationKey, DidVerificationKeyRelationship}; @@ -32,8 +35,11 @@ use pallet_dip_consumer::traits::IdentityProofVerifier; use crate::{ did::{RevealedDidKeysAndSignature, RevealedDidKeysSignatureAndCallVerifier, TimeBoundDidSignature}, merkle::{DidMerkleProof, DidMerkleProofVerifier, RevealedDidMerkleProofLeaf, RevealedDidMerkleProofLeaves}, - state_proofs::{parachain::DipIdentityCommitmentProofVerifier, relay_chain::SiblingParachainHeadProofVerifier}, - traits::{Bump, DidSignatureVerifierContext, DipCallOriginFilter}, + state_proofs::{parachain::DipIdentityCommitmentProofVerifier, relay_chain::ParachainHeadProofVerifier}, + traits::{ + Bump, DidSignatureVerifierContext, DipCallOriginFilter, HistoricalBlockRegistry, ProviderParachainStateInfo, + RelayChainStorageInfo, + }, utils::OutputOf, }; @@ -43,6 +49,8 @@ pub mod state_proofs; pub mod traits; pub mod utils; +pub use state_proofs::relay_chain::RococoStateRootsViaRelayStorePallet; + #[derive(Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo, Clone)] pub struct SiblingParachainDipStateProof< RelayBlockHeight, @@ -50,13 +58,13 @@ pub struct SiblingParachainDipStateProof< DipMerkleProofRevealedLeaf, DipProviderBlockNumber, > { - para_state_root: SiblingParachainRootStateProof, + para_state_root: ParachainRootStateProof, dip_identity_commitment: Vec>, did: DipMerkleProofAndDidSignature, } #[derive(Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo, Clone)] -pub struct SiblingParachainRootStateProof { +pub struct ParachainRootStateProof { relay_block_height: RelayBlockHeight, proof: Vec>, } @@ -67,6 +75,7 @@ pub struct DipMerkleProofAndDidSignature { signature: TimeBoundDidSignature, } +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub struct DipSiblingProviderStateProofVerifier< RelayChainStateInfo, SiblingProviderParachainId, @@ -133,8 +142,7 @@ impl< Call: Encode, TxSubmitter: Encode, - RelayChainStateInfo: traits::RelayChainStateInfo, - RelayChainStateInfo::Hasher: 'static, + RelayChainStateInfo: traits::RelayChainStorageInfo + traits::RelayChainStateInfo, OutputOf: Ord, RelayChainStateInfo::BlockNumber: Copy + Into + TryFrom + HasCompact, RelayChainStateInfo::Key: AsRef<[u8]>, @@ -143,7 +151,6 @@ impl< SiblingProviderStateInfo: traits::ProviderParachainStateInfo, - SiblingProviderStateInfo::Hasher: 'static, OutputOf: Ord + From>, SiblingProviderStateInfo::BlockNumber: Encode + Clone, SiblingProviderStateInfo::Commitment: Decode, @@ -192,12 +199,11 @@ impl< proof: Self::Proof, ) -> Result { // 1. Verify relay chain proof. - let provider_parachain_header = - SiblingParachainHeadProofVerifier::::verify_proof_for_parachain( - &SiblingProviderParachainId::get(), - &proof.para_state_root.relay_block_height, - proof.para_state_root.proof, - )?; + let provider_parachain_header = ParachainHeadProofVerifier::::verify_proof_for_parachain( + &SiblingProviderParachainId::get(), + &proof.para_state_root.relay_block_height, + proof.para_state_root.proof, + )?; // 2. Verify parachain state proof. let subject_identity_commitment = @@ -242,6 +248,214 @@ impl< } } -pub use state_proofs::{ - parachain::KiltDipCommitmentsForDipProviderPallet, relay_chain::RococoStateRootsViaRelayStorePallet, -}; +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct ChildParachainDipStateProof< + RelayBlockHeight: Copy + Into + TryFrom, + RelayBlockHasher: Hash, + DipMerkleProofBlindedValues, + DipMerkleProofRevealedLeaf, + DipProviderBlockNumber, +> { + para_state_root: ParachainRootStateProof, + relay_header: Header, + dip_identity_commitment: Vec>, + did: DipMerkleProofAndDidSignature, +} + +pub struct DipChildProviderStateProofVerifier< + RelayChainInfo, + SiblingProviderParachainId, + SiblingProviderStateInfo, + TxSubmitter, + ProviderDipMerkleHasher, + ProviderDidKeyId, + ProviderWeb3Name, + ProviderLinkedAccountId, + const MAX_REVEALED_KEYS_COUNT: u32, + const MAX_REVEALED_ACCOUNTS_COUNT: u32, + LocalDidDetails, + LocalContextProvider, + LocalDidCallVerifier, +>( + #[allow(clippy::type_complexity)] + PhantomData<( + RelayChainInfo, + SiblingProviderParachainId, + SiblingProviderStateInfo, + TxSubmitter, + ProviderDipMerkleHasher, + ProviderDidKeyId, + ProviderWeb3Name, + ProviderLinkedAccountId, + LocalDidDetails, + LocalContextProvider, + LocalDidCallVerifier, + )>, +); + +impl< + Call, + Subject, + RelayChainInfo, + SiblingProviderParachainId, + SiblingProviderStateInfo, + TxSubmitter, + ProviderDipMerkleHasher, + ProviderDidKeyId, + ProviderWeb3Name, + ProviderLinkedAccountId, + const MAX_REVEALED_KEYS_COUNT: u32, + const MAX_REVEALED_ACCOUNTS_COUNT: u32, + LocalDidDetails, + LocalContextProvider, + LocalDidCallVerifier, + > IdentityProofVerifier + for DipChildProviderStateProofVerifier< + RelayChainInfo, + SiblingProviderParachainId, + SiblingProviderStateInfo, + TxSubmitter, + ProviderDipMerkleHasher, + ProviderDidKeyId, + ProviderWeb3Name, + ProviderLinkedAccountId, + MAX_REVEALED_KEYS_COUNT, + MAX_REVEALED_ACCOUNTS_COUNT, + LocalDidDetails, + LocalContextProvider, + LocalDidCallVerifier, + > where + Call: Encode, + TxSubmitter: Encode, + + RelayChainInfo: RelayChainStorageInfo + + HistoricalBlockRegistry< + BlockNumber = ::BlockNumber, + Hasher = ::Hasher, + >, + OutputOf<::Hasher>: + Ord + Default + sp_std::hash::Hash + Copy + Member + MaybeDisplay + SimpleBitOps + Codec, + ::BlockNumber: Copy + + Into + + TryFrom + + HasCompact + + Member + + sp_std::hash::Hash + + MaybeDisplay + + AtLeast32BitUnsigned + + Codec, + RelayChainInfo::Key: AsRef<[u8]>, + + SiblingProviderParachainId: Get, + + SiblingProviderStateInfo: + ProviderParachainStateInfo, + OutputOf: Ord + From::Hasher>>, + SiblingProviderStateInfo::BlockNumber: Encode + Clone, + SiblingProviderStateInfo::Commitment: Decode, + SiblingProviderStateInfo::Key: AsRef<[u8]>, + + LocalContextProvider: DidSignatureVerifierContext, + LocalContextProvider::BlockNumber: Encode + CheckedSub + From + PartialOrd, + LocalContextProvider::Hash: Encode, + LocalContextProvider::SignedExtra: Encode, + LocalDidDetails: Bump + Default + Encode, + LocalDidCallVerifier: DipCallOriginFilter, + + ProviderDipMerkleHasher: sp_core::Hasher, + ProviderDidKeyId: Encode + Clone + Into, + ProviderLinkedAccountId: Encode + Clone, + ProviderWeb3Name: Encode + Clone, +{ + type Error = (); + type IdentityDetails = LocalDidDetails; + type Proof = ChildParachainDipStateProof< + ::BlockNumber, + ::Hasher, + Vec>, + RevealedDidMerkleProofLeaf< + ProviderDidKeyId, + SiblingProviderStateInfo::BlockNumber, + ProviderWeb3Name, + ProviderLinkedAccountId, + >, + LocalContextProvider::BlockNumber, + >; + type Submitter = TxSubmitter; + type VerificationResult = RevealedDidMerkleProofLeaves< + ProviderDidKeyId, + SiblingProviderStateInfo::BlockNumber, + ProviderWeb3Name, + ProviderLinkedAccountId, + MAX_REVEALED_KEYS_COUNT, + MAX_REVEALED_ACCOUNTS_COUNT, + >; + + fn verify_proof_for_call_against_details( + call: &Call, + subject: &Subject, + submitter: &Self::Submitter, + identity_details: &mut Option, + proof: Self::Proof, + ) -> Result { + // 1. Retrieve block hash from provider at the proof height + let block_hash_at_height = + RelayChainInfo::block_hash_for(&proof.para_state_root.relay_block_height).ok_or(())?; + + // 1.1 Verify that the provided header hashes to the same block has retrieved + if block_hash_at_height != proof.relay_header.hash() { + return Err(()); + } + // 1.2 If so, extract the state root from the header + let state_root_at_height = proof.relay_header.state_root; + + // FIXME: Compilation error + // 2. Verify relay chain proof + let provider_parachain_header = + ParachainHeadProofVerifier::::verify_proof_for_parachain_with_root( + &SiblingProviderParachainId::get(), + &state_root_at_height, + proof.para_state_root.proof, + )?; + + // 3. Verify parachain state proof. + let subject_identity_commitment = + DipIdentityCommitmentProofVerifier::::verify_proof_for_identifier( + subject, + provider_parachain_header.state_root.into(), + proof.dip_identity_commitment, + )?; + + // 4. Verify DIP merkle proof. + let proof_leaves = DidMerkleProofVerifier::< + ProviderDipMerkleHasher, + _, + _, + _, + _, + MAX_REVEALED_KEYS_COUNT, + MAX_REVEALED_ACCOUNTS_COUNT, + >::verify_dip_merkle_proof(&subject_identity_commitment, proof.did.leaves)?; + + // 5. Verify DID signature. + RevealedDidKeysSignatureAndCallVerifier::< + _, + _, + _, + _, + LocalContextProvider, + _, + _, + LocalDidCallVerifier, + >::verify_did_signature_for_call( + call, + submitter, + identity_details, + RevealedDidKeysAndSignature { + merkle_leaves: proof_leaves.borrow(), + did_signature: proof.did.signature, + }, + )?; + Ok(proof_leaves) + } +} diff --git a/crates/kilt-dip-support/src/state_proofs.rs b/crates/kilt-dip-support/src/state_proofs.rs index 814b2b55e4..0e8bf02562 100644 --- a/crates/kilt-dip-support/src/state_proofs.rs +++ b/crates/kilt-dip-support/src/state_proofs.rs @@ -45,7 +45,7 @@ mod substrate_no_std_port { keys: I, ) -> Result, Option>>, ()> where - H: Hasher + 'static, + H: Hasher, H::Out: Ord + Codec, I: IntoIterator, I::Item: AsRef<[u8]>, @@ -90,33 +90,28 @@ pub(super) mod relay_chain { use sp_runtime::traits::BlakeTwo256; - use crate::traits::RelayChainStateInfo; + use crate::traits::{RelayChainStateInfo, RelayChainStorageInfo}; - pub struct SiblingParachainHeadProofVerifier(PhantomData); + pub struct ParachainHeadProofVerifier(PhantomData); - impl SiblingParachainHeadProofVerifier + // Uses the provided `root` to verify the proof. + impl ParachainHeadProofVerifier where - RelayChainState: RelayChainStateInfo, - RelayChainState::Hasher: 'static, + RelayChainState: RelayChainStorageInfo, OutputOf: Ord, RelayChainState::BlockNumber: Copy + Into + TryFrom + HasCompact, RelayChainState::Key: AsRef<[u8]>, { - #[allow(clippy::result_unit_err)] - pub fn verify_proof_for_parachain( + pub fn verify_proof_for_parachain_with_root( para_id: &RelayChainState::ParaId, - relay_height: &RelayChainState::BlockNumber, + root: &OutputOf<::Hasher>, proof: impl IntoIterator>, ) -> Result, ()> { - let relay_state_root = RelayChainState::state_root_for_block(relay_height).ok_or(())?; let parachain_storage_key = RelayChainState::parachain_head_storage_key(para_id); let storage_proof = StorageProof::new(proof); - let revealed_leaves = read_proof_check::( - relay_state_root, - storage_proof, - [¶chain_storage_key].iter(), - ) - .map_err(|_| ())?; + let revealed_leaves = + read_proof_check::(*root, storage_proof, [¶chain_storage_key].iter()) + .map_err(|_| ())?; // TODO: Remove at some point { debug_assert!(revealed_leaves.len() == 1usize); @@ -129,9 +124,29 @@ pub(super) mod relay_chain { } } + // Relies on the `RelayChainState::state_root_for_block` to retrieve the state + // root for the given block. + impl ParachainHeadProofVerifier + where + RelayChainState: RelayChainStateInfo, + OutputOf: Ord, + RelayChainState::BlockNumber: Copy + Into + TryFrom + HasCompact, + RelayChainState::Key: AsRef<[u8]>, + { + #[allow(clippy::result_unit_err)] + pub fn verify_proof_for_parachain( + para_id: &RelayChainState::ParaId, + relay_height: &RelayChainState::BlockNumber, + proof: impl IntoIterator>, + ) -> Result, ()> { + let relay_state_root = RelayChainState::state_root_for_block(relay_height).ok_or(())?; + Self::verify_proof_for_parachain_with_root(para_id, &relay_state_root, proof) + } + } + pub struct RococoStateRootsViaRelayStorePallet(PhantomData); - impl RelayChainStateInfo for RococoStateRootsViaRelayStorePallet + impl RelayChainStorageInfo for RococoStateRootsViaRelayStorePallet where Runtime: pallet_relay_store::Config, { @@ -140,11 +155,6 @@ pub(super) mod relay_chain { type Key = StorageKey; type ParaId = u32; - fn state_root_for_block(block_height: &Self::BlockNumber) -> Option> { - pallet_relay_store::Pallet::::latest_relay_head_for_block(block_height) - .map(|relay_header| relay_header.relay_parent_storage_root) - } - fn parachain_head_storage_key(para_id: &Self::ParaId) -> Self::Key { // TODO: It's not possible to access the runtime definition from here. let encoded_para_id = para_id.encode(); @@ -158,6 +168,16 @@ pub(super) mod relay_chain { } } + impl RelayChainStateInfo for RococoStateRootsViaRelayStorePallet + where + Runtime: pallet_relay_store::Config, + { + fn state_root_for_block(block_height: &Self::BlockNumber) -> Option> { + pallet_relay_store::Pallet::::latest_relay_head_for_block(block_height) + .map(|relay_header| relay_header.relay_parent_storage_root) + } + } + #[cfg(test)] mod polkadot_parachain_head_proof_verifier_tests { use super::*; @@ -169,7 +189,7 @@ pub(super) mod relay_chain { // hash 0x18e90e9aa8e3b063f60386ba1b0415111798e72d01de58b1438d620d42f58e39 struct StaticPolkadotInfoProvider; - impl RelayChainStateInfo for StaticPolkadotInfoProvider { + impl RelayChainStorageInfo for StaticPolkadotInfoProvider { type BlockNumber = u32; type Hasher = BlakeTwo256; type Key = StorageKey; @@ -188,7 +208,9 @@ pub(super) mod relay_chain { .concat(); StorageKey(storage_key) } + } + impl RelayChainStateInfo for StaticPolkadotInfoProvider { fn state_root_for_block(_block_height: &Self::BlockNumber) -> Option> { Some(hex!("81b75d95075d16005ee0a987a3f061d3011ada919b261e9b02961b9b3725f3fd").into()) } @@ -210,13 +232,12 @@ pub(super) mod relay_chain { // "0xcd710b30bd2eab0352ddcc26417aa1941b3c252fcb29d88eff4f3de5de4476c32c0cfd6c23b92a7826080000" // let expected_spiritnet_head_at_block = hex!("65541097fb02782e14f43074f0b00e44ae8e9fe426982323ef1d329739740d37f252ff006d1156941db1bccd58ce3a1cac4f40cad91f692d94e98f501dd70081a129b69a3e2ef7e1ff84ba3d86dab4e95f2c87f6b1055ebd48519c185360eae58f05d1ea08066175726120dcdc6308000000000561757261010170ccfaf3756d1a8dd8ae5c89094199d6d32e5dd9f0920f6fe30f986815b5e701974ea0e0e0a901401f2c72e3dd8dbdf4aa55d59bf3e7021856cdb8038419eb8c").to_vec(); - let returned_head = - SiblingParachainHeadProofVerifier::::verify_proof_for_parachain( - &2_086, - &16_363_919, - spiritnet_head_proof_at_block, - ) - .expect("Parachain head proof verification should not fail."); + let returned_head = ParachainHeadProofVerifier::::verify_proof_for_parachain( + &2_086, + &16_363_919, + spiritnet_head_proof_at_block, + ) + .expect("Parachain head proof verification should not fail."); assert!(returned_head.encode() == expected_spiritnet_head_at_block, "Parachain head returned from the state proof verification should not be different than the pre-computed one."); } } @@ -232,7 +253,6 @@ pub(super) mod parachain { impl DipIdentityCommitmentProofVerifier where ParaInfo: ProviderParachainStateInfo, - ParaInfo::Hasher: 'static, OutputOf: Ord, ParaInfo::Commitment: Decode, ParaInfo::Key: AsRef<[u8]>, @@ -261,31 +281,6 @@ pub(super) mod parachain { } } - pub struct KiltDipCommitmentsForDipProviderPallet(PhantomData); - - impl ProviderParachainStateInfo for KiltDipCommitmentsForDipProviderPallet - where - Runtime: pallet_dip_provider::Config, - { - type BlockNumber = ::BlockNumber; - type Commitment = ::IdentityCommitment; - type Hasher = ::Hashing; - type Identifier = ::Identifier; - type Key = StorageKey; - - fn dip_subject_storage_key(identifier: &Self::Identifier) -> Self::Key { - // TODO: Replace with actual runtime definition - let encoded_identifier = identifier.encode(); - let storage_key = [ - frame_support::storage::storage_prefix(b"DipProvider", b"IdentityCommitments").as_slice(), - sp_io::hashing::twox_64(&encoded_identifier).as_slice(), - encoded_identifier.as_slice(), - ] - .concat(); - StorageKey(storage_key) - } - } - #[cfg(test)] mod spiritnet_test_event_count_value { use super::*; diff --git a/crates/kilt-dip-support/src/traits.rs b/crates/kilt-dip-support/src/traits.rs index ac65ed945c..2477d82a4f 100644 --- a/crates/kilt-dip-support/src/traits.rs +++ b/crates/kilt-dip-support/src/traits.rs @@ -16,6 +16,7 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org +use sp_core::storage::StorageKey; use sp_runtime::traits::{CheckedAdd, One, Zero}; use sp_std::marker::PhantomData; @@ -54,16 +55,19 @@ pub trait DipCallOriginFilter { fn check_call_origin_info(call: &Call, info: &Self::OriginInfo) -> Result; } -pub trait RelayChainStateInfo { +pub trait RelayChainStorageInfo { type BlockNumber; - type Key; type Hasher: sp_runtime::traits::Hash; + type Key; type ParaId; - fn state_root_for_block(block_height: &Self::BlockNumber) -> Option>; fn parachain_head_storage_key(para_id: &Self::ParaId) -> Self::Key; } +pub trait RelayChainStateInfo: RelayChainStorageInfo { + fn state_root_for_block(block_height: &Self::BlockNumber) -> Option>; +} + pub trait ProviderParachainStateInfo { type BlockNumber; type Commitment; @@ -74,6 +78,25 @@ pub trait ProviderParachainStateInfo { fn dip_subject_storage_key(identifier: &Self::Identifier) -> Self::Key; } +pub struct ProviderParachainStateInfoViaProviderPallet(PhantomData); + +impl ProviderParachainStateInfo for ProviderParachainStateInfoViaProviderPallet +where + T: pallet_dip_provider::Config, +{ + type BlockNumber = T::BlockNumber; + type Commitment = T::IdentityCommitment; + type Hasher = T::Hashing; + type Identifier = T::Identifier; + type Key = StorageKey; + + fn dip_subject_storage_key(identifier: &Self::Identifier) -> Self::Key { + StorageKey(pallet_dip_provider::IdentityCommitments::::hashed_key_for( + identifier, + )) + } +} + pub trait DidSignatureVerifierContext { const SIGNATURE_VALIDITY: u16; @@ -109,3 +132,29 @@ where fn signed_extra() -> Self::SignedExtra {} } + +pub trait HistoricalBlockRegistry { + type BlockNumber; + type Hasher: sp_runtime::traits::Hash; + + fn block_hash_for(block: &Self::BlockNumber) -> Option>; +} + +impl HistoricalBlockRegistry for T +where + T: frame_system::Config, +{ + type BlockNumber = T::BlockNumber; + type Hasher = T::Hashing; + + fn block_hash_for(block: &Self::BlockNumber) -> Option> { + let retrieved_block = frame_system::Pallet::::block_hash(block); + let default_block_hash_value = ::default(); + + if retrieved_block == default_block_hash_value { + None + } else { + Some(retrieved_block) + } + } +} diff --git a/dip-template/nodes/dip-consumer/src/chain_spec.rs b/dip-template/nodes/dip-consumer/src/chain_spec.rs index d80daa51d2..bfc123f067 100644 --- a/dip-template/nodes/dip-consumer/src/chain_spec.rs +++ b/dip-template/nodes/dip-consumer/src/chain_spec.rs @@ -99,7 +99,6 @@ fn testnet_genesis( }, aura: Default::default(), aura_ext: Default::default(), - did_lookup: Default::default(), } } diff --git a/dip-template/pallets/pallet-postit/Cargo.toml b/dip-template/pallets/pallet-postit/Cargo.toml new file mode 100644 index 0000000000..dbfc82136b --- /dev/null +++ b/dip-template/pallets/pallet-postit/Cargo.toml @@ -0,0 +1,40 @@ +[package] +authors.workspace = true +documentation.workspace = true +edition.workspace = true +homepage.workspace = true +license-file.workspace = true +readme.workspace = true +repository.workspace = true +version.workspace = true +name = "pallet-postit" +description = "Simple pallet to store on-chain comments, replies, and likes." + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +# External dependencies +parity-scale-codec = {workspace = true, features = ["derive"]} +scale-info = {workspace = true, features = ["derive"]} + +#External dependencies +frame-support.workspace = true +frame-system.workspace = true +sp-runtime.workspace = true +sp-std.workspace = true + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks" +] +std = [ + "parity-scale-codec/std", + "scale-info/std", + "frame-support/std", + "frame-system/std", + "sp-runtime/std", + "sp-std/std", +] diff --git a/dip-template/pallets/pallet-postit/src/lib.rs b/dip-template/pallets/pallet-postit/src/lib.rs new file mode 100644 index 0000000000..170797f61e --- /dev/null +++ b/dip-template/pallets/pallet-postit/src/lib.rs @@ -0,0 +1,185 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod post; +pub mod traits; + +pub use pallet::*; + +#[frame_support::pallet(dev_mode)] +pub mod pallet { + + use super::*; + + use frame_support::{ + pallet_prelude::{DispatchResult, *}, + traits::EnsureOrigin, + BoundedVec, + }; + use frame_system::pallet_prelude::*; + use sp_runtime::traits::Hash; + use sp_std::fmt::Debug; + + use crate::{ + post::{Comment, Post}, + traits::Usernamable, + }; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + + pub type BoundedTextOf = BoundedVec::MaxTextLength>; + pub type PostOf = Post<::Hash, BoundedTextOf, ::Username>; + pub type CommentOf = Comment<::Hash, BoundedTextOf, ::Username>; + + #[pallet::config] + pub trait Config: frame_system::Config { + type MaxTextLength: Get; + type OriginCheck: EnsureOrigin<::RuntimeOrigin, Success = Self::OriginSuccess>; + type OriginSuccess: Usernamable; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type Username: Encode + Decode + TypeInfo + MaxEncodedLen + Clone + PartialEq + Debug + Default; + } + + #[pallet::storage] + #[pallet::getter(fn posts)] + pub type Posts = StorageMap<_, Twox64Concat, ::Hash, PostOf>; + + #[pallet::storage] + #[pallet::getter(fn comments)] + pub type Comments = StorageMap<_, Twox64Concat, ::Hash, CommentOf>; + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + NewPost { + post_id: T::Hash, + author: T::Username, + }, + NewComment { + resource_id: T::Hash, + comment_id: T::Hash, + author: T::Username, + }, + NewLike { + resource_id: T::Hash, + liker: T::Username, + }, + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(1_000)] + pub fn post(origin: OriginFor, text: BoundedTextOf) -> DispatchResult { + let success_origin = T::OriginCheck::ensure_origin(origin)?; + let author = success_origin.username().map_err(DispatchError::Other)?; + let post_id = T::Hashing::hash( + (&frame_system::Pallet::::block_number(), &author, &text) + .encode() + .as_slice(), + ); + let post = PostOf::::from_text_and_author(text, author.clone()); + Posts::::insert(post_id, post); + Self::deposit_event(Event::NewPost { post_id, author }); + Ok(()) + } + + #[pallet::call_index(1)] + #[pallet::weight(1_000)] + pub fn comment(origin: OriginFor, resource_id: T::Hash, text: BoundedTextOf) -> DispatchResult { + let success_origin = T::OriginCheck::ensure_origin(origin)?; + let author = success_origin.username().map_err(DispatchError::Other)?; + let comment_id = T::Hashing::hash( + (&frame_system::Pallet::::block_number(), &author, &text) + .encode() + .as_slice(), + ); + Posts::::try_mutate(resource_id, |post| { + if let Some(post) = post { + post.comments + .try_push(comment_id) + .expect("Failed to add comment to post."); + Ok(()) + } else { + Err(()) + } + }) + .or_else(|_| { + Comments::::try_mutate(resource_id, |comment| { + if let Some(comment) = comment { + comment + .details + .comments + .try_push(comment_id) + .expect("Failed to add comment to comment."); + Ok(()) + } else { + Err(()) + } + }) + }) + .map_err(|_| DispatchError::Other("No post or comment with provided ID found."))?; + let comment = CommentOf::::from_post_id_text_and_author(resource_id, text, author.clone()); + Comments::::insert(comment_id, comment); + Self::deposit_event(Event::NewComment { + resource_id, + comment_id, + author, + }); + Ok(()) + } + + #[pallet::call_index(2)] + #[pallet::weight(1_000)] + pub fn like(origin: OriginFor, resource_id: T::Hash) -> DispatchResult { + let success_origin = T::OriginCheck::ensure_origin(origin)?; + let liker = success_origin.username().map_err(DispatchError::Other)?; + Posts::::try_mutate(resource_id, |post| { + if let Some(post) = post { + post.likes.try_push(liker.clone()).expect("Failed to add like to post."); + Ok(()) + } else { + Err(()) + } + }) + .or_else(|_| { + Comments::::try_mutate(resource_id, |comment| { + if let Some(comment) = comment { + comment + .details + .likes + .try_push(liker.clone()) + .expect("Failed to add like to comment."); + Ok(()) + } else { + Err(()) + } + }) + }) + .map_err(|_| DispatchError::Other("No post or comment with provided ID found."))?; + Self::deposit_event(Event::NewLike { resource_id, liker }); + Ok(()) + } + } +} diff --git a/dip-template/pallets/pallet-postit/src/post.rs b/dip-template/pallets/pallet-postit/src/post.rs new file mode 100644 index 0000000000..49fd158fc9 --- /dev/null +++ b/dip-template/pallets/pallet-postit/src/post.rs @@ -0,0 +1,55 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use frame_support::{traits::ConstU32, BoundedVec}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, Default)] +pub struct Post { + pub author: Username, + pub text: Text, + pub likes: BoundedVec>, + pub comments: BoundedVec>, +} + +impl Post { + pub(crate) fn from_text_and_author(text: Text, author: Username) -> Self { + Self { + text, + author, + likes: BoundedVec::default(), + comments: BoundedVec::default(), + } + } +} + +#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, Default)] +pub struct Comment { + pub details: Post, + pub in_response_to: Id, +} + +impl Comment { + pub(crate) fn from_post_id_text_and_author(in_response_to: Id, text: Text, author: Username) -> Self { + Self { + in_response_to, + details: Post::from_text_and_author(text, author), + } + } +} diff --git a/dip-template/pallets/pallet-postit/src/traits.rs b/dip-template/pallets/pallet-postit/src/traits.rs new file mode 100644 index 0000000000..8a1d20f580 --- /dev/null +++ b/dip-template/pallets/pallet-postit/src/traits.rs @@ -0,0 +1,23 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +pub trait Usernamable { + type Username; + + fn username(&self) -> Result; +} diff --git a/dip-template/runtimes/dip-consumer/Cargo.toml b/dip-template/runtimes/dip-consumer/Cargo.toml index 578eeff8a0..aae167b098 100644 --- a/dip-template/runtimes/dip-consumer/Cargo.toml +++ b/dip-template/runtimes/dip-consumer/Cargo.toml @@ -23,6 +23,7 @@ did.workspace = true kilt-dip-support.workspace = true pallet-did-lookup.workspace = true pallet-dip-consumer.workspace = true +pallet-postit.workspace = true pallet-relay-store.workspace = true runtime-common.workspace = true @@ -75,6 +76,7 @@ std = [ "kilt-dip-support/std", "pallet-did-lookup/std", "pallet-dip-consumer/std", + "pallet-postit/std", "pallet-relay-store/std", "runtime-common/std", "frame-executive/std", diff --git a/dip-template/runtimes/dip-consumer/src/dip.rs b/dip-template/runtimes/dip-consumer/src/dip.rs index 78d25919df..11b355dd7f 100644 --- a/dip-template/runtimes/dip-consumer/src/dip.rs +++ b/dip-template/runtimes/dip-consumer/src/dip.rs @@ -20,8 +20,8 @@ use did::{did_details::DidVerificationKey, DidVerificationKeyRelationship, KeyId use dip_provider_runtime_template::{Runtime as ProviderRuntime, Web3Name}; use frame_support::traits::Contains; use kilt_dip_support::{ - traits::{DipCallOriginFilter, FrameSystemDidSignatureContext}, - DipSiblingProviderStateProofVerifier, KiltDipCommitmentsForDipProviderPallet, RococoStateRootsViaRelayStorePallet, + traits::{DipCallOriginFilter, FrameSystemDidSignatureContext, ProviderParachainStateInfoViaProviderPallet}, + DipSiblingProviderStateProofVerifier, RococoStateRootsViaRelayStorePallet, }; use pallet_did_lookup::linkable_account::LinkableAccountId; use pallet_dip_consumer::traits::IdentityProofVerifier; @@ -30,10 +30,12 @@ use sp_runtime::traits::BlakeTwo256; use crate::{AccountId, DidIdentifier, Runtime, RuntimeCall, RuntimeOrigin}; +pub type MerkleProofVerifierOutputOf = + >::VerificationResult; pub type ProofVerifier = DipSiblingProviderStateProofVerifier< RococoStateRootsViaRelayStorePallet, ConstU32<2_000>, - KiltDipCommitmentsForDipProviderPallet, + ProviderParachainStateInfoViaProviderPallet, AccountId, BlakeTwo256, KeyIdOf, @@ -63,7 +65,7 @@ impl Contains for PreliminaryDipOriginFilter { fn contains(t: &RuntimeCall) -> bool { matches!( t, - RuntimeCall::DidLookup { .. } + RuntimeCall::PostIt { .. } | RuntimeCall::Utility(pallet_utility::Call::batch { .. }) | RuntimeCall::Utility(pallet_utility::Call::batch_all { .. }) | RuntimeCall::Utility(pallet_utility::Call::force_batch { .. }) @@ -73,7 +75,7 @@ impl Contains for PreliminaryDipOriginFilter { fn derive_verification_key_relationship(call: &RuntimeCall) -> Option { match call { - RuntimeCall::DidLookup { .. } => Some(DidVerificationKeyRelationship::Authentication), + RuntimeCall::PostIt { .. } => Some(DidVerificationKeyRelationship::Authentication), RuntimeCall::Utility(pallet_utility::Call::batch { calls }) => single_key_relationship(calls.iter()).ok(), RuntimeCall::Utility(pallet_utility::Call::batch_all { calls }) => single_key_relationship(calls.iter()).ok(), RuntimeCall::Utility(pallet_utility::Call::force_batch { calls }) => single_key_relationship(calls.iter()).ok(), @@ -121,47 +123,6 @@ impl DipCallOriginFilter for DipCallFilter { } } -#[cfg(test)] -mod dip_call_origin_filter_tests { - use super::*; - - use frame_support::assert_err; - - #[test] - fn test_key_relationship_derivation() { - // Can call DidLookup functions with an authentication key - let did_lookup_call = RuntimeCall::DidLookup(pallet_did_lookup::Call::associate_sender {}); - assert_eq!( - single_key_relationship(vec![did_lookup_call].iter()), - Ok(DidVerificationKeyRelationship::Authentication) - ); - // Can't call System functions with a DID key (hence a DIP origin) - let system_call = RuntimeCall::System(frame_system::Call::remark { remark: vec![] }); - assert_err!(single_key_relationship(vec![system_call].iter()), ()); - // Can't call empty batch with a DID key - let empty_batch_call = RuntimeCall::Utility(pallet_utility::Call::batch_all { calls: vec![] }); - assert_err!(single_key_relationship(vec![empty_batch_call].iter()), ()); - // Can call batch with a DipLookup with an authentication key - let did_lookup_batch_call = RuntimeCall::Utility(pallet_utility::Call::batch_all { - calls: vec![pallet_did_lookup::Call::associate_sender {}.into()], - }); - assert_eq!( - single_key_relationship(vec![did_lookup_batch_call].iter()), - Ok(DidVerificationKeyRelationship::Authentication) - ); - // Can't call a batch with different required keys - let did_lookup_batch_call = RuntimeCall::Utility(pallet_utility::Call::batch_all { - calls: vec![ - // Authentication key - pallet_did_lookup::Call::associate_sender {}.into(), - // No key - frame_system::Call::remark { remark: vec![] }.into(), - ], - }); - assert_err!(single_key_relationship(vec![did_lookup_batch_call].iter()), ()); - } -} - impl pallet_relay_store::Config for Runtime { type MaxRelayBlocksStored = ConstU32<100>; } diff --git a/dip-template/runtimes/dip-consumer/src/lib.rs b/dip-template/runtimes/dip-consumer/src/lib.rs index 2aa417ea22..5a0c38bbd7 100644 --- a/dip-template/runtimes/dip-consumer/src/lib.rs +++ b/dip-template/runtimes/dip-consumer/src/lib.rs @@ -22,10 +22,7 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); -use did::KeyIdOf; use dip_provider_runtime_template::Web3Name; -use kilt_dip_support::merkle::RevealedDidMerkleProofLeaves; -use pallet_did_lookup::linkable_account::LinkableAccountId; pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; pub use sp_runtime::{MultiAddress, Perbill, Permill}; @@ -51,7 +48,6 @@ use frame_system::{ }; use pallet_balances::AccountData; use pallet_collator_selection::IdentityCollator; -use pallet_dip_consumer::{DipOrigin, EnsureDipOrigin}; use pallet_session::{FindAccountFromAuthorIndex, PeriodicSessions}; use pallet_transaction_payment::{CurrencyAdapter, FeeDetails, RuntimeDispatchInfo}; use sp_api::impl_runtime_apis; @@ -68,7 +64,8 @@ use sp_std::{prelude::*, time::Duration}; use sp_version::RuntimeVersion; mod dip; -pub use crate::dip::*; +mod origin_adapter; +pub use crate::{dip::*, origin_adapter::*}; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; @@ -135,8 +132,8 @@ construct_runtime!( Aura: pallet_aura = 23, AuraExt: cumulus_pallet_aura_ext = 24, - // DID lookup - DidLookup: pallet_did_lookup = 30, + // PostIt + PostIt: pallet_postit = 30, // DIP DipConsumer: pallet_dip_consumer = 40, @@ -356,27 +353,12 @@ impl pallet_aura::Config for Runtime { impl cumulus_pallet_aura_ext::Config for Runtime {} -parameter_types! { - pub const LinkDeposit: Balance = UNIT; -} - -impl pallet_did_lookup::Config for Runtime { - type Currency = Balances; - type Deposit = ConstU128; - type DidIdentifier = DidIdentifier; - type EnsureOrigin = EnsureDipOrigin< - DidIdentifier, - AccountId, - RevealedDidMerkleProofLeaves, BlockNumber, Web3Name, LinkableAccountId, 10, 10>, - >; - type OriginSuccess = DipOrigin< - DidIdentifier, - AccountId, - RevealedDidMerkleProofLeaves, BlockNumber, Web3Name, LinkableAccountId, 10, 10>, - >; +impl pallet_postit::Config for Runtime { + type MaxTextLength = ConstU32<160>; + type OriginCheck = EnsureDipOriginAdapter; + type OriginSuccess = DipOriginAdapter; type RuntimeEvent = RuntimeEvent; - type RuntimeHoldReason = RuntimeHoldReason; - type WeightInfo = (); + type Username = Web3Name; } impl_runtime_apis! { diff --git a/dip-template/runtimes/dip-consumer/src/origin_adapter.rs b/dip-template/runtimes/dip-consumer/src/origin_adapter.rs new file mode 100644 index 0000000000..afb428e509 --- /dev/null +++ b/dip-template/runtimes/dip-consumer/src/origin_adapter.rs @@ -0,0 +1,53 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The KILT Blockchain is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use crate::{AccountId, DidIdentifier, MerkleProofVerifierOutputOf, RuntimeCall, RuntimeOrigin, Web3Name}; +use frame_support::traits::EnsureOrigin; +use pallet_dip_consumer::{DipOrigin, EnsureDipOrigin}; +use pallet_postit::traits::Usernamable; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_core::RuntimeDebug; + +pub struct EnsureDipOriginAdapter; + +impl EnsureOrigin for EnsureDipOriginAdapter { + type Success = DipOriginAdapter; + + fn try_origin(o: RuntimeOrigin) -> Result { + EnsureDipOrigin::try_origin(o).map(DipOriginAdapter) + } +} + +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct DipOriginAdapter( + DipOrigin>, +); + +impl Usernamable for DipOriginAdapter { + type Username = Web3Name; + + fn username(&self) -> Result { + self.0 + .details + .web3_name + .as_ref() + .map(|leaf| leaf.web3_name.clone()) + .ok_or("No username for the subject.") + } +} diff --git a/dip-template/runtimes/dip-provider/src/lib.rs b/dip-template/runtimes/dip-provider/src/lib.rs index 010f9d9186..f8b97a98ec 100644 --- a/dip-template/runtimes/dip-provider/src/lib.rs +++ b/dip-template/runtimes/dip-provider/src/lib.rs @@ -188,7 +188,8 @@ register_validate_block! { CheckInherents = CheckInherents, } -pub const SS58_PREFIX: u16 = 100; +// Same as official KILT prefix. +pub const SS58_PREFIX: u16 = 38; const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(5); const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts(