diff --git a/Cargo.lock b/Cargo.lock index 4ea5562ee..69a1fae44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2344,6 +2344,7 @@ dependencies = [ "frame-support", "frame-system", "frame-system-rpc-runtime-api", + "kilt-runtime-api-dip-sender", "pallet-aura", "pallet-authorship", "pallet-balances", @@ -2359,6 +2360,7 @@ dependencies = [ "parachain-info", "parity-scale-codec", "polkadot-parachain", + "runtime-common", "scale-info", "sp-api", "sp-block-builder", @@ -2457,6 +2459,7 @@ dependencies = [ "pallet-xcm", "parachain-info", "parity-scale-codec", + "runtime-common", "scale-info", "sp-api", "sp-block-builder", @@ -2486,24 +2489,31 @@ dependencies = [ ] [[package]] -name = "dip-templates-xmc-tests" +name = "dip-templates-xcm-tests" version = "1.11.0-dev" dependencies = [ "cumulus-pallet-xcmp-queue", + "did", "dip-receiver-runtime-template", "dip-sender-runtime-template", + "dip-support", "frame-support", "frame-system", + "kilt-support", "pallet-balances", + "pallet-did-lookup", "parachain-info", "parity-scale-codec", "polkadot-parachain", "polkadot-primitives", "polkadot-runtime-parachains", "rococo-runtime", + "runtime-common", "scale-info", + "sp-core", "sp-io", "sp-runtime", + "sp-std", "xcm", "xcm-emulator", "xcm-executor", @@ -4273,6 +4283,14 @@ dependencies = [ "sp-std", ] +[[package]] +name = "kilt-runtime-api-dip-sender" +version = "1.11.0-dev" +dependencies = [ + "parity-scale-codec", + "sp-api", +] + [[package]] name = "kilt-runtime-api-public-credentials" version = "1.11.0-dev" @@ -9380,6 +9398,7 @@ dependencies = [ "cumulus-primitives-core", "delegation", "did", + "dip-support", "frame-support", "frame-system", "kilt-asset-dids", @@ -9388,6 +9407,8 @@ dependencies = [ "pallet-authorship", "pallet-balances", "pallet-did-lookup", + "pallet-dip-receiver", + "pallet-dip-sender", "pallet-inflation", "pallet-membership", "pallet-transaction-payment", @@ -9403,6 +9424,7 @@ dependencies = [ "sp-io", "sp-runtime", "sp-std", + "sp-trie", "xcm", "xcm-builder", "xcm-executor", diff --git a/Cargo.toml b/Cargo.toml index 95442c91b..292ea62c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,6 +71,7 @@ dip-sender-runtime-template = {path = "dip-template/runtimes/dip-sender", defaul # Internal runtime API (with default disabled) kilt-runtime-api-did = {path = "runtime-api/did", default-features = false} +kilt-runtime-api-dip-sender = {path = "runtime-api/dip-sender", default-features = false} kilt-runtime-api-public-credentials = {path = "runtime-api/public-credentials", default-features = false} kilt-runtime-api-staking = {path = "runtime-api/staking", default-features = false} @@ -136,6 +137,7 @@ sp-session = {git = "https://github.com/paritytech/substrate", default-features sp-staking = {git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.39"} sp-std = {git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.39"} sp-transaction-pool = {git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.39"} +sp-trie = {git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.39"} sp-version = {git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.39"} sp-weights = {git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.39"} try-runtime-cli = {git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.39"} diff --git a/crates/dip/src/lib.rs b/crates/dip/src/lib.rs index f681976fa..a795d7c85 100644 --- a/crates/dip/src/lib.rs +++ b/crates/dip/src/lib.rs @@ -31,6 +31,7 @@ pub mod latest { } #[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[non_exhaustive] pub enum VersionedIdentityProofAction { #[codec(index = 1)] V1(v1::IdentityProofAction), @@ -45,13 +46,28 @@ impl From { +#[non_exhaustive] +pub enum VersionedIdentityProof { #[codec(index = 1)] - V1(v1::Proof), + V1(v1::Proof), } -impl From> for VersionedIdentityProof { - fn from(value: v1::Proof) -> Self { +impl From> for VersionedIdentityProof { + fn from(value: v1::Proof) -> Self { Self::V1(value) } } + +impl TryFrom> for v1::Proof { + // Proper error handling + type Error = (); + + fn try_from(value: VersionedIdentityProof) -> Result { + #[allow(irrefutable_let_patterns)] + if let VersionedIdentityProof::V1(v1::Proof { blinded, revealed }) = value { + Ok(Self { blinded, revealed }) + } else { + Err(()) + } + } +} diff --git a/crates/dip/src/v1.rs b/crates/dip/src/v1.rs index f72e02881..bdbda97bf 100644 --- a/crates/dip/src/v1.rs +++ b/crates/dip/src/v1.rs @@ -28,4 +28,8 @@ pub enum IdentityProofAction { } #[derive(Encode, Decode, RuntimeDebug, Clone, Eq, PartialEq, TypeInfo, Default)] -pub struct Proof(Vec<(LeafKey, LeafValue)>); +pub struct Proof { + pub blinded: BlindedValue, + // TODO: Probably replace with a different data structure for better lookup capabilities + pub revealed: Vec, +} diff --git a/dip-template/runtimes/dip-receiver/Cargo.toml b/dip-template/runtimes/dip-receiver/Cargo.toml index d41f73dba..1737cf152 100644 --- a/dip-template/runtimes/dip-receiver/Cargo.toml +++ b/dip-template/runtimes/dip-receiver/Cargo.toml @@ -20,6 +20,7 @@ scale-info = {workspace = true, features = ["derive"]} # DIP pallet-did-lookup.workspace = true pallet-dip-receiver.workspace = true +runtime-common.workspace = true # Substrate frame-executive.workspace = true @@ -74,6 +75,7 @@ std = [ "scale-info/std", "pallet-did-lookup/std", "pallet-dip-receiver/std", + "runtime-common/std", "frame-executive/std", "frame-support/std", "frame-system/std", @@ -118,6 +120,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "frame-support/runtime-benchmarks", "pallet-dip-receiver/runtime-benchmarks", + "runtime-common/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", ] diff --git a/dip-template/runtimes/dip-receiver/src/dip.rs b/dip-template/runtimes/dip-receiver/src/dip.rs index b36d425ab..18a5ff1b0 100644 --- a/dip-template/runtimes/dip-receiver/src/dip.rs +++ b/dip-template/runtimes/dip-receiver/src/dip.rs @@ -16,20 +16,17 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org -use pallet_dip_receiver::traits::SuccessfulProofVerifier; +use runtime_common::dip::{receiver::DidMerkleProofVerifier, ProofLeaf}; +use sp_std::vec::Vec; -use crate::{DidIdentifier, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin}; +use crate::{BlockNumber, DidIdentifier, Hash, Hasher, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin}; impl pallet_dip_receiver::Config for Runtime { + type BlindedValue = Vec>; type Identifier = DidIdentifier; - // TODO: Change with right one - type ProofDigest = [u8; 32]; - // TODO: Change with right one - type ProofLeafKey = [u8; 4]; - // TODO: Change with right one - type ProofLeafValue = [u8; 4]; - // TODO: Change with right one - type ProofVerifier = SuccessfulProofVerifier; + type ProofLeaf = ProofLeaf; + type ProofDigest = Hash; + type ProofVerifier = DidMerkleProofVerifier; type RuntimeCall = RuntimeCall; type RuntimeEvent = RuntimeEvent; type RuntimeOrigin = RuntimeOrigin; diff --git a/dip-template/runtimes/dip-receiver/src/lib.rs b/dip-template/runtimes/dip-receiver/src/lib.rs index 6dd9a5219..c678bf0c2 100644 --- a/dip-template/runtimes/dip-receiver/src/lib.rs +++ b/dip-template/runtimes/dip-receiver/src/lib.rs @@ -56,9 +56,9 @@ use sp_core::{crypto::KeyTypeId, ConstU128, ConstU16, OpaqueMetadata}; use sp_inherents::{CheckInherentsResult, InherentData}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, IdentifyAccount, OpaqueKeys, Verify}, + traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, OpaqueKeys}, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, MultiSignature, OpaqueExtrinsic, + AccountId32, ApplyExtrinsicResult, MultiSignature, OpaqueExtrinsic, }; use sp_std::{prelude::*, time::Duration}; use sp_version::RuntimeVersion; @@ -73,14 +73,15 @@ pub use sp_runtime::BuildStorage; #[cfg(feature = "std")] use sp_version::NativeVersion; -pub type AccountId = <::Signer as IdentifyAccount>::AccountId; +pub type AccountId = AccountId32; pub type Address = MultiAddress; pub type Balance = u128; pub type Block = generic::Block; pub type BlockNumber = u32; pub type DidIdentifier = AccountId; +pub type Hasher = BlakeTwo256; pub type Hash = sp_core::H256; -pub type Header = generic::Header; +pub type Header = generic::Header; pub type Index = u32; pub type Signature = MultiSignature; @@ -149,7 +150,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("dip-receiver-runtime-template"), impl_name: create_runtime_str!("dip-receiver-runtime-template"), authoring_version: 1, - spec_version: 1, + spec_version: 11100, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -228,8 +229,8 @@ impl frame_system::Config for Runtime { type BlockWeights = RuntimeBlockWeights; type DbWeight = RocksDbWeight; type Hash = Hash; - type Hashing = BlakeTwo256; - type Header = generic::Header; + type Hashing = Hasher; + type Header = generic::Header; type Index = Index; type Lookup = AccountIdLookup; type MaxConsumers = ConstU32<16>; diff --git a/dip-template/runtimes/dip-sender/Cargo.toml b/dip-template/runtimes/dip-sender/Cargo.toml index d9972fd45..3d638bb4d 100644 --- a/dip-template/runtimes/dip-sender/Cargo.toml +++ b/dip-template/runtimes/dip-sender/Cargo.toml @@ -20,7 +20,9 @@ scale-info = {workspace = true, features = ["derive"]} # DIP did.workspace = true dip-support.workspace = true +kilt-runtime-api-dip-sender.workspace = true pallet-dip-sender.workspace = true +runtime-common.workspace = true # Substrate frame-executive.workspace = true @@ -74,7 +76,9 @@ std = [ "scale-info/std", "did/std", "dip-support/std", + "kilt-runtime-api-dip-sender/std", "pallet-dip-sender/std", + "runtime-common/std", "frame-executive/std", "frame-support/std", "frame-system/std", @@ -115,9 +119,10 @@ std = [ ] runtime-benchmarks = [ "did/runtime-benchmarks", + "pallet-dip-sender/runtime-benchmarks", + "runtime-common/runtime-benchmarks", "frame-system/runtime-benchmarks", "frame-support/runtime-benchmarks", - "pallet-dip-sender/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "xcm-builder/runtime-benchmarks" ] diff --git a/dip-template/runtimes/dip-sender/src/dip.rs b/dip-template/runtimes/dip-sender/src/dip.rs index a31ed8b39..a8afc90ac 100644 --- a/dip-template/runtimes/dip-sender/src/dip.rs +++ b/dip-template/runtimes/dip-sender/src/dip.rs @@ -16,14 +16,14 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org +use did::did_details::DidDetails; use dip_support::VersionedIdentityProofAction; -use pallet_dip_sender::traits::{ - DefaultIdentityProofGenerator, DefaultIdentityProvider, TxBuilder, XcmRouterDispatcher, -}; +use pallet_dip_sender::traits::{TxBuilder, XcmRouterDispatcher}; use parity_scale_codec::{Decode, Encode}; +use runtime_common::dip::sender::{DidIdentityProvider, DidMerkleRootGenerator}; use xcm::{latest::MultiLocation, DoubleEncoded}; -use crate::{DidIdentifier, Runtime, RuntimeEvent, XcmRouter}; +use crate::{DidIdentifier, Hash, Runtime, RuntimeEvent, XcmRouter}; #[derive(Encode, Decode)] enum ReceiverParachainCalls { @@ -34,16 +34,16 @@ enum ReceiverParachainCalls { #[derive(Encode, Decode)] enum ReceiverParachainDipReceiverCalls { #[codec(index = 0)] - ProcessIdentityAction(VersionedIdentityProofAction), + ProcessIdentityAction(VersionedIdentityProofAction), } pub struct ReceiverParachainTxBuilder; -impl TxBuilder for ReceiverParachainTxBuilder { +impl TxBuilder for ReceiverParachainTxBuilder { type Error = (); fn build( _dest: MultiLocation, - action: VersionedIdentityProofAction, + action: VersionedIdentityProofAction, ) -> Result, Self::Error> { let double_encoded: DoubleEncoded<()> = ReceiverParachainCalls::DipReceiver(ReceiverParachainDipReceiverCalls::ProcessIdentityAction(action)) @@ -55,16 +55,11 @@ impl TxBuilder for ReceiverParachainTxBuilder { impl pallet_dip_sender::Config for Runtime { type Identifier = DidIdentifier; - // TODO: Change with right one - type Identity = u32; - // TODO: Change with right one - type IdentityProofDispatcher = XcmRouterDispatcher; - // TODO: Change with right one - type IdentityProofGenerator = DefaultIdentityProofGenerator; - // TODO: Change with right one - type IdentityProvider = DefaultIdentityProvider; - // TODO: Change with right one - type ProofOutput = [u8; 32]; + type Identity = DidDetails; + type IdentityProofDispatcher = XcmRouterDispatcher; + type IdentityProofGenerator = DidMerkleRootGenerator; + type IdentityProvider = DidIdentityProvider; + type ProofOutput = Hash; type RuntimeEvent = RuntimeEvent; type TxBuilder = ReceiverParachainTxBuilder; } diff --git a/dip-template/runtimes/dip-sender/src/lib.rs b/dip-template/runtimes/dip-sender/src/lib.rs index d81684c1e..d13871665 100644 --- a/dip-template/runtimes/dip-sender/src/lib.rs +++ b/dip-template/runtimes/dip-sender/src/lib.rs @@ -30,7 +30,7 @@ use cumulus_pallet_parachain_system::{ }; use cumulus_primitives_core::CollationInfo; use cumulus_primitives_timestamp::InherentDataProvider; -use did::{DidRawOrigin, EnsureDidOrigin}; +use did::{DidRawOrigin, EnsureDidOrigin, KeyIdOf}; use frame_support::{ construct_runtime, dispatch::DispatchClass, @@ -48,17 +48,19 @@ use frame_system::{ }; use pallet_balances::AccountData; use pallet_collator_selection::IdentityCollator; +use pallet_dip_sender::traits::IdentityProvider; use pallet_session::{FindAccountFromAuthorIndex, PeriodicSessions}; use pallet_transaction_payment::{CurrencyAdapter, FeeDetails, RuntimeDispatchInfo}; +use runtime_common::dip::sender::{CompleteMerkleProof, DidMerkleProof, DidMerkleRootGenerator}; use sp_api::impl_runtime_apis; use sp_consensus_aura::SlotDuration; use sp_core::{crypto::KeyTypeId, ConstU128, ConstU16, OpaqueMetadata}; use sp_inherents::{CheckInherentsResult, InherentData}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, IdentifyAccount, OpaqueKeys, Verify}, + traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, OpaqueKeys}, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, MultiSignature, OpaqueExtrinsic, + AccountId32, ApplyExtrinsicResult, MultiSignature, OpaqueExtrinsic, }; use sp_std::{prelude::*, time::Duration}; use sp_version::RuntimeVersion; @@ -73,7 +75,7 @@ mod dip; mod xcm_config; pub use crate::{dip::*, xcm_config::*}; -pub type AccountId = <::Signer as IdentifyAccount>::AccountId; +pub type AccountId = AccountId32; pub type Address = MultiAddress; pub type Balance = u128; pub type Block = generic::Block; @@ -149,7 +151,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("dip-sender-runtime-template"), impl_name: create_runtime_str!("dip-sender-runtime-template"), authoring_version: 1, - spec_version: 1, + spec_version: 11100, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -525,4 +527,16 @@ impl_runtime_apis! { ParachainSystem::collect_collation_info(header) } } + + // TODO: `keys` could and should be a BTreeSet, but it makes it more complicated for clients to build the type. So we use a Vec, since the keys are deduplicated anyway at proof creation time. + // TODO: Support generating different versions of the proof, based on the provided parameter + impl kilt_runtime_api_dip_sender::DipSender, Vec>, CompleteMerkleProof>, ()> for Runtime { + fn generate_proof(identifier: DidIdentifier, keys: Vec>) -> Result>, ()> { + if let Ok(Some((did_details, _))) = ::IdentityProvider::retrieve(&identifier) { + DidMerkleRootGenerator::::generate_proof(&did_details, keys.iter()) + } else { + Err(()) + } + } + } } diff --git a/dip-template/runtimes/xcm-tests/Cargo.toml b/dip-template/runtimes/xcm-tests/Cargo.toml index 26c770a85..4c032a3e9 100644 --- a/dip-template/runtimes/xcm-tests/Cargo.toml +++ b/dip-template/runtimes/xcm-tests/Cargo.toml @@ -7,27 +7,32 @@ license-file.workspace = true readme.workspace = true repository.workspace = true version.workspace = true -name = "dip-templates-xmc-tests" +name = "dip-templates-xcm-tests" description = "XCM integration tests for the KILT Decentralized Identity Provider (DIP) sender and receiver templates." -[dev-dependencies] -cumulus-pallet-xcmp-queue = { workspace = true, features = ["std"] } - [dependencies] -parity-scale-codec = {workspace = true, features = ["std", "derive"]} +cumulus-pallet-xcmp-queue = { workspace = true, features = ["std"] } +did = { workspace = true, features = ["std"] } dip-receiver-runtime-template = { workspace = true, features = ["std"] } dip-sender-runtime-template = { workspace = true, features = ["std"] } +dip-support = { workspace = true, features = ["std"] } frame-support = { workspace = true, features = ["std"] } frame-system = { workspace = true, features = ["std"] } +kilt-support = { workspace = true, features = ["std"] } pallet-balances = { workspace = true, features = ["std"] } +pallet-did-lookup = { workspace = true, features = ["std"] } parachain-info = { workspace = true, features = ["std"] } +parity-scale-codec = {workspace = true, features = ["std", "derive"]} polkadot-parachain = { workspace = true, features = ["std"] } polkadot-primitives = { workspace = true, features = ["std"] } polkadot-runtime-parachains = { workspace = true, features = ["std"] } rococo-runtime = { workspace = true, features = ["std"] } +runtime-common = { workspace = true, features = ["std"] } scale-info = { workspace = true, features = ["std"] } +sp-core = { workspace = true, features = ["std"] } sp-io = { workspace = true, features = ["std"] } sp-runtime = { workspace = true, features = ["std"] } +sp-std = { workspace = true, features = ["std"] } xcm = { workspace = true, features = ["std"] } xcm-emulator = { git = "https://github.com/shaunxw/xcm-simulator", branch = "master" } xcm-executor = { workspace = true, features = ["std"] } diff --git a/dip-template/runtimes/xcm-tests/src/para.rs b/dip-template/runtimes/xcm-tests/src/para.rs index ca30e62ba..15fc36c46 100644 --- a/dip-template/runtimes/xcm-tests/src/para.rs +++ b/dip-template/runtimes/xcm-tests/src/para.rs @@ -22,15 +22,44 @@ use sp_io::TestExternalities; use xcm_emulator::decl_test_parachain; pub(super) mod sender { - pub(crate) use dip_sender_runtime_template::{DmpQueue, Runtime, RuntimeOrigin, XcmpQueue}; + pub(crate) use dip_sender_runtime_template::{DidIdentifier, DmpQueue, Runtime, RuntimeOrigin, XcmpQueue}; + + use did::did_details::{DidDetails, DidEncryptionKey, DidVerificationKey}; + use dip_sender_runtime_template::{AccountId, System}; + use kilt_support::deposit::Deposit; + use sp_core::{ecdsa, ed25519, sr25519, Pair}; use super::*; pub const PARA_ID: u32 = 2_000; - pub(crate) fn para_ext() -> TestExternalities { - use dip_sender_runtime_template::System; + pub(crate) fn did_auth_key() -> ed25519::Pair { + ed25519::Pair::from_seed(&[200u8; 32]) + } + fn generate_did_details() -> DidDetails { + let auth_key: DidVerificationKey = did_auth_key().public().into(); + let att_key: DidVerificationKey = sr25519::Pair::from_seed(&[100u8; 32]).public().into(); + let del_key: DidVerificationKey = ecdsa::Pair::from_seed(&[101u8; 32]).public().into(); + + let mut details = DidDetails::new( + auth_key, + 0u32, + Deposit { + amount: 1u64.into(), + owner: AccountId::new([1u8; 32]), + }, + ) + .unwrap(); + details.update_attestation_key(att_key, 0u32).unwrap(); + details.update_delegation_key(del_key, 0u32).unwrap(); + details + .add_key_agreement_key(DidEncryptionKey::X25519([100u8; 32]), 0u32) + .unwrap(); + details + } + + pub(crate) fn para_ext() -> TestExternalities { let mut t = frame_system::GenesisConfig::default() .build_storage::() .unwrap(); @@ -43,7 +72,10 @@ pub(super) mod sender { .unwrap(); let mut ext = TestExternalities::new(t); + let did: DidIdentifier = did_auth_key().public().into(); + let details = generate_did_details(); ext.execute_with(|| { + did::pallet::Did::::insert(&did, details); System::set_block_number(1); }); ext @@ -62,16 +94,18 @@ pub(super) mod sender { pub(super) mod receiver { pub(crate) use dip_receiver_runtime_template::{ - AccountId, AssetTransactorLocationConverter, Balance, DmpQueue, Runtime, RuntimeOrigin, XcmpQueue, + AccountId, AssetTransactorLocationConverter, Balance, DmpQueue, Runtime, RuntimeOrigin, XcmpQueue, UNIT, }; + use dip_receiver_runtime_template::System; use xcm::latest::{Junction::Parachain, Junctions::X1, ParentThen}; use xcm_executor::traits::Convert; use super::*; pub const PARA_ID: u32 = 2_001; - const INITIAL_BALANCE: Balance = 1_000_000_000; + pub const DISPATCHER_ACCOUNT: AccountId = AccountId::new([90u8; 32]); + const INITIAL_BALANCE: Balance = 100_000 * UNIT; pub(crate) fn sender_parachain_account() -> AccountId { AssetTransactorLocationConverter::convert(ParentThen(X1(Parachain(sender::PARA_ID))).into()) @@ -79,8 +113,6 @@ pub(super) mod receiver { } pub(crate) fn para_ext() -> TestExternalities { - use dip_receiver_runtime_template::System; - let mut t = frame_system::GenesisConfig::default() .build_storage::() .unwrap(); @@ -93,7 +125,10 @@ pub(super) mod receiver { .unwrap(); pallet_balances::GenesisConfig:: { - balances: vec![(sender_parachain_account(), INITIAL_BALANCE)], + balances: vec![ + (sender_parachain_account(), INITIAL_BALANCE), + (DISPATCHER_ACCOUNT, INITIAL_BALANCE), + ], } .assimilate_storage(&mut t) .unwrap(); diff --git a/dip-template/runtimes/xcm-tests/src/tests.rs b/dip-template/runtimes/xcm-tests/src/tests.rs index 037561708..e8cabc399 100644 --- a/dip-template/runtimes/xcm-tests/src/tests.rs +++ b/dip-template/runtimes/xcm-tests/src/tests.rs @@ -18,13 +18,13 @@ use super::*; -use dip_receiver_runtime_template::{ - AccountId as ReceiverAccountId, DidIdentifier as ReceiverDidIdentifier, DipReceiver, -}; -use dip_sender_runtime_template::DipSender; - +use did::Did; +use dip_support::latest::Proof; use frame_support::{assert_ok, weights::Weight}; use frame_system::RawOrigin; +use pallet_did_lookup::linkable_account::LinkableAccountId; +use runtime_common::dip::sender::{CompleteMerkleProof, DidMerkleRootGenerator}; +use sp_core::Pair; use xcm::latest::{ Junction::Parachain, Junctions::{Here, X1}, @@ -32,23 +32,24 @@ use xcm::latest::{ }; use xcm_emulator::TestExt; +use cumulus_pallet_xcmp_queue::Event as XcmpEvent; +use dip_receiver_runtime_template::{ + DidIdentifier, DidLookup, DipReceiver, Runtime as ReceiverRuntime, RuntimeCall as ReceiverRuntimeCall, + RuntimeEvent, System, +}; +use dip_sender_runtime_template::{AccountId as SenderAccountId, DipSender, Runtime as SenderRuntime}; + #[test] fn commit_identity() { Network::reset(); - ReceiverParachain::execute_with(|| { - use dip_receiver_runtime_template::Balances; - use para::receiver::sender_parachain_account; - - let sender_balance = Balances::free_balance(sender_parachain_account()); - println!("Sender balance: {:?}", sender_balance); - }); + let did: DidIdentifier = para::sender::did_auth_key().public().into(); // 1. Send identity proof from DIP sender to DIP receiver. SenderParachain::execute_with(|| { assert_ok!(DipSender::commit_identity( - RawOrigin::Signed(ReceiverAccountId::from([0u8; 32])).into(), - ReceiverDidIdentifier::from([0u8; 32]), + RawOrigin::Signed(SenderAccountId::from([0u8; 32])).into(), + did.clone(), Box::new(ParentThen(X1(Parachain(para::receiver::PARA_ID))).into()), Box::new((Here, 1_000_000_000).into()), Weight::from_ref_time(4_000), @@ -56,9 +57,6 @@ fn commit_identity() { }); // 2. Verify that the proof has made it to the DIP receiver. ReceiverParachain::execute_with(|| { - use cumulus_pallet_xcmp_queue::Event as XcmpEvent; - use dip_receiver_runtime_template::{RuntimeEvent, System}; - // 2.1 Verify that there was no XCM error. assert!(!System::events().iter().any(|r| matches!( r.event, @@ -68,8 +66,34 @@ fn commit_identity() { weight: _ }) ))); - // 2.2 Verify the proof digest is the same that was sent. - let details = DipReceiver::identity_proofs(dip_sender_runtime_template::AccountId::from([0u8; 32])); - assert_eq!(details, Some([0u8; 32])); + // 2.2 Verify the proof digest was stored correctly. + assert!(DipReceiver::identity_proofs(&did).is_some()); + }); + // 3. Call an extrinsic on the receiver chain with a valid proof + let did_details = + SenderParachain::execute_with(|| Did::get(&did).expect("DID details should be stored on the sender chain.")); + // 3.1 Generate a proof + let CompleteMerkleProof { proof, .. } = + DidMerkleRootGenerator::::generate_proof(&did_details, [did_details.authentication_key].iter()) + .expect("Proof generation should not fail"); + // 3.2 Call the `dispatch_as` extrinsic on the receiver chain with the generated + // proof + ReceiverParachain::execute_with(|| { + assert_ok!(DipReceiver::dispatch_as( + RawOrigin::Signed(para::receiver::DISPATCHER_ACCOUNT).into(), + did.clone(), + Proof { + blinded: proof.blinded, + revealed: proof.revealed, + } + .into(), + Box::new(ReceiverRuntimeCall::DidLookup(pallet_did_lookup::Call::< + ReceiverRuntime, + >::associate_sender {})), + )); + // Verify the account -> DID link exists and contains the right information + let linked_did = DidLookup::connected_dids::(para::receiver::DISPATCHER_ACCOUNT.into()) + .map(|link| link.did); + assert_eq!(linked_did, Some(did)); }); } diff --git a/pallets/did/src/did_details.rs b/pallets/did/src/did_details.rs index c1e42fe7f..27a347bb5 100644 --- a/pallets/did/src/did_details.rs +++ b/pallets/did/src/did_details.rs @@ -121,7 +121,7 @@ impl From for DidPublicKey { /// Verification methods a verification key can /// fulfil, according to the [DID specification](https://w3c.github.io/did-spec-registries/#verification-relationships). -#[derive(Clone, Copy, RuntimeDebug, Decode, Encode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[derive(Clone, Copy, RuntimeDebug, Decode, Encode, PartialEq, Eq, TypeInfo, MaxEncodedLen, PartialOrd, Ord)] pub enum DidVerificationKeyRelationship { /// Key used to authenticate all the DID operations. Authentication, @@ -239,9 +239,7 @@ impl> DidVerifiableIdentifier for I { /// It is currently used to keep track of all the past and current /// attestation keys a DID might control. #[derive(Clone, RuntimeDebug, Decode, Encode, PartialEq, Ord, PartialOrd, Eq, TypeInfo, MaxEncodedLen)] -#[scale_info(skip_type_params(T))] -#[codec(mel_bound())] -pub struct DidPublicKeyDetails { +pub struct DidPublicKeyDetails { /// A public key the DID controls. pub key: DidPublicKey, /// The block number in which the verification key was added to the DID. diff --git a/pallets/pallet-dip-receiver/Cargo.toml b/pallets/pallet-dip-receiver/Cargo.toml index 8e7e5a1ac..6a6d6f98d 100644 --- a/pallets/pallet-dip-receiver/Cargo.toml +++ b/pallets/pallet-dip-receiver/Cargo.toml @@ -37,5 +37,5 @@ std = [ ] runtime-benchmarks = [ "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks" + "frame-system/runtime-benchmarks", ] diff --git a/pallets/pallet-dip-receiver/src/lib.rs b/pallets/pallet-dip-receiver/src/lib.rs index 551b6bc80..cb93b7228 100644 --- a/pallets/pallet-dip-receiver/src/lib.rs +++ b/pallets/pallet-dip-receiver/src/lib.rs @@ -39,7 +39,7 @@ pub mod pallet { use crate::traits::IdentityProofVerifier; pub type VersionedIdentityProofOf = - VersionedIdentityProof<::ProofLeafKey, ::ProofLeafValue>; + VersionedIdentityProof<::BlindedValue, ::ProofLeaf>; const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); @@ -51,14 +51,14 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { + type BlindedValue: Parameter; type Identifier: Parameter + MaxEncodedLen; - type ProofLeafKey: Parameter; - type ProofLeafValue: Parameter; + type ProofLeaf: Parameter; type ProofDigest: Parameter + MaxEncodedLen; type ProofVerifier: IdentityProofVerifier< + BlindedValue = Self::BlindedValue, ProofDigest = Self::ProofDigest, - LeafKey = Self::ProofLeafKey, - LeafValue = Self::ProofLeafValue, + ProofLeaf = Self::ProofLeaf, >; type RuntimeCall: Parameter + Dispatchable::RuntimeOrigin>; type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -84,6 +84,7 @@ pub mod pallet { Dispatch, IdentityNotFound, InvalidProof, + UnsupportedVersion, } // The new origin other pallets can use. @@ -104,13 +105,14 @@ pub mod pallet { let event = match action { VersionedIdentityProofAction::V1(IdentityProofAction::Updated(identifier, proof, _)) => { IdentityProofs::::mutate(&identifier, |entry| *entry = Some(proof.clone())); - Event::::IdentityInfoUpdated(identifier, proof) + Ok::<_, Error>(Event::::IdentityInfoUpdated(identifier, proof)) } VersionedIdentityProofAction::V1(IdentityProofAction::Deleted(identifier)) => { IdentityProofs::::remove(&identifier); - Event::::IdentityInfoDeleted(identifier) + Ok::<_, Error>(Event::::IdentityInfoDeleted(identifier)) } - }; + _ => Err(Error::::UnsupportedVersion), + }?; Self::deposit_event(event); diff --git a/pallets/pallet-dip-receiver/src/traits.rs b/pallets/pallet-dip-receiver/src/traits.rs index 460dc48b0..ef7642fff 100644 --- a/pallets/pallet-dip-receiver/src/traits.rs +++ b/pallets/pallet-dip-receiver/src/traits.rs @@ -20,31 +20,31 @@ use dip_support::VersionedIdentityProof; use sp_std::marker::PhantomData; pub trait IdentityProofVerifier { + type BlindedValue; + type Error; type ProofDigest; - type LeafKey; - type LeafValue; + type ProofLeaf; type VerificationResult; - type Error; fn verify_proof_against_digest( - proof: VersionedIdentityProof, + proof: VersionedIdentityProof, digest: Self::ProofDigest, ) -> Result; } // Always returns success. -pub struct SuccessfulProofVerifier(PhantomData<(ProofDigest, LeafKey, LeafValue)>); -impl IdentityProofVerifier - for SuccessfulProofVerifier +pub struct SuccessfulProofVerifier(PhantomData<(ProofDigest, Leaf, BlindedValue)>); +impl IdentityProofVerifier + for SuccessfulProofVerifier { - type ProofDigest = ProofDigest; + type BlindedValue = BlindedValue; type Error = (); - type LeafKey = LeafKey; - type LeafValue = LeafValue; + type ProofDigest = ProofDigest; + type ProofLeaf = Leaf; type VerificationResult = (); fn verify_proof_against_digest( - _proof: VersionedIdentityProof, + _proof: VersionedIdentityProof, _digest: Self::ProofDigest, ) -> Result { Ok(()) diff --git a/pallets/pallet-dip-sender/src/lib.rs b/pallets/pallet-dip-sender/src/lib.rs index 57d3c38fa..4af958ccd 100644 --- a/pallets/pallet-dip-sender/src/lib.rs +++ b/pallets/pallet-dip-sender/src/lib.rs @@ -48,7 +48,11 @@ pub mod pallet { type Identifier: Parameter; type Identity; type ProofOutput: Clone + Eq + Debug; - type IdentityProofGenerator: IdentityProofGenerator; + type IdentityProofGenerator: IdentityProofGenerator< + Self::Identifier, + Self::Identity, + Output = Self::ProofOutput, + >; type IdentityProofDispatcher: IdentityProofDispatcher; type IdentityProvider: IdentityProvider; type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -98,7 +102,7 @@ pub mod pallet { let destination: MultiLocation = (*destination).try_into().map_err(|_| Error::::BadVersion)?; let action: IdentityProofActionOf = match T::IdentityProvider::retrieve(&identifier) { Ok(Some((identity, _))) => { - let identity_proof = T::IdentityProofGenerator::generate_proof(&identifier, &identity) + let identity_proof = T::IdentityProofGenerator::generate_commitment(&identifier, &identity) .map_err(|_| Error::::IdentityProofGeneration)?; Ok(IdentityProofAction::Updated(identifier, identity_proof, ())) } diff --git a/pallets/pallet-dip-sender/src/traits.rs b/pallets/pallet-dip-sender/src/traits.rs index 097bd6a37..6667699cc 100644 --- a/pallets/pallet-dip-sender/src/traits.rs +++ b/pallets/pallet-dip-sender/src/traits.rs @@ -21,25 +21,28 @@ use xcm::{latest::prelude::*, DoubleEncoded}; pub use identity_generation::*; pub mod identity_generation { + use sp_std::marker::PhantomData; - pub trait IdentityProofGenerator { + pub trait IdentityProofGenerator { type Error; + type Output; - fn generate_proof(identifier: &Identifier, identity: &Identity) -> Result; + fn generate_commitment(identifier: &Identifier, identity: &Identity) -> Result; } // Implement the `IdentityProofGenerator` by returning the `Default` value for // the `Output` type. - pub struct DefaultIdentityProofGenerator; + pub struct DefaultIdentityProofGenerator(PhantomData); - impl IdentityProofGenerator - for DefaultIdentityProofGenerator + impl IdentityProofGenerator + for DefaultIdentityProofGenerator where Output: Default, { type Error = (); + type Output = Output; - fn generate_proof(_identifier: &Identifier, _identity: &Identity) -> Result { + fn generate_commitment(_identifier: &Identifier, _identity: &Identity) -> Result { Ok(Output::default()) } } diff --git a/runtime-api/did/src/did_details.rs b/runtime-api/did/src/did_details.rs index 509aa7292..84af78fe4 100644 --- a/runtime-api/did/src/did_details.rs +++ b/runtime-api/did/src/did_details.rs @@ -24,7 +24,7 @@ use did::{did_details::DidPublicKeyDetails, AccountIdOf, BalanceOf, BlockNumberO use kilt_support::deposit::Deposit; #[derive(Encode, Decode, TypeInfo, Clone, Debug, Eq, PartialEq, MaxEncodedLen)] -pub struct DidDetails { +pub struct DidDetails { pub authentication_key: Key, pub key_agreement_keys: BTreeSet, pub delegation_key: Option, diff --git a/runtime-api/did/src/lib.rs b/runtime-api/did/src/lib.rs index 291e77960..52a6f6e36 100644 --- a/runtime-api/did/src/lib.rs +++ b/runtime-api/did/src/lib.rs @@ -39,7 +39,7 @@ pub struct DidLinkedInfo< Url, Balance, Key: Ord, - BlockNumber: MaxEncodedLen, + BlockNumber, > { pub identifier: DidIdentifier, pub accounts: Vec, diff --git a/runtime-api/dip-sender/Cargo.toml b/runtime-api/dip-sender/Cargo.toml new file mode 100644 index 000000000..12b20140f --- /dev/null +++ b/runtime-api/dip-sender/Cargo.toml @@ -0,0 +1,27 @@ +[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 = "kilt-runtime-api-dip-sender" +description = "Runtime APIs for integrating the DIP sender component." + +[dependencies] +# External dependencies +parity-scale-codec.workspace = true + +# Internal dependencies + +# Substrate dependencies +sp-api.workspace = true + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "sp-api/std" +] diff --git a/runtime-api/dip-sender/src/lib.rs b/runtime-api/dip-sender/src/lib.rs new file mode 100644 index 000000000..ce420714e --- /dev/null +++ b/runtime-api/dip-sender/src/lib.rs @@ -0,0 +1,33 @@ +// 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)] + +use parity_scale_codec::Codec; + +sp_api::decl_runtime_apis! { + pub trait DipSender where + DidIdentifier: Codec, + KeyId: Codec, + KeyIds: Codec + IntoIterator, + Success: Codec, + Error: Codec, + { + fn generate_proof(identifier: DidIdentifier, keys: KeyIds) -> Result; + } +} diff --git a/runtimes/common/Cargo.toml b/runtimes/common/Cargo.toml index f6c49d646..c9467dd67 100644 --- a/runtimes/common/Cargo.toml +++ b/runtimes/common/Cargo.toml @@ -25,8 +25,11 @@ smallvec.workspace = true attestation.workspace = true ctype.workspace = true delegation = {workspace = true, optional = true} -did = {workspace = true, optional = true} +did.workspace = true +dip-support.workspace = true pallet-did-lookup = {workspace = true, optional = true} +pallet-dip-receiver.workspace = true +pallet-dip-sender.workspace = true pallet-inflation = {workspace = true, optional = true} kilt-support.workspace = true parachain-staking.workspace = true @@ -45,6 +48,7 @@ sp-core.workspace = true sp-io.workspace = true sp-runtime.workspace = true sp-std.workspace = true +sp-trie.workspace = true # Cumulus dependencies cumulus-primitives-core.workspace = true @@ -61,6 +65,8 @@ fast-gov = [] runtime-benchmarks = [ "attestation/runtime-benchmarks", "ctype/runtime-benchmarks", + "did/runtime-benchmarks", + "pallet-dip-receiver/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "kilt-support/runtime-benchmarks", @@ -77,6 +83,8 @@ std = [ "attestation/std", "ctype/std", "cumulus-primitives-core/std", + "did/std", + "dip-support/std", "frame-support/std", "frame-system/std", "kilt-asset-dids/std", @@ -84,6 +92,8 @@ std = [ "log/std", "pallet-authorship/std", "pallet-balances/std", + "pallet-dip-receiver/std", + "pallet-dip-sender/std", "pallet-membership/std", "pallet-transaction-payment/std", "parachain-staking/std", @@ -96,6 +106,7 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-std/std", + "sp-trie/std", "xcm-builder/std", "xcm-executor/std", "xcm/std", @@ -103,7 +114,6 @@ std = [ try-runtime = [ "attestation/try-runtime", "delegation", - "did", "frame-support/try-runtime", "frame-system/try-runtime", "kilt-support/try-runtime", diff --git a/runtimes/common/src/dip/mod.rs b/runtimes/common/src/dip/mod.rs new file mode 100644 index 000000000..5f292b366 --- /dev/null +++ b/runtimes/common/src/dip/mod.rs @@ -0,0 +1,82 @@ +// 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 did::{did_details::DidPublicKeyDetails, DidVerificationKeyRelationship}; +use frame_support::RuntimeDebug; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_std::vec::Vec; + +pub mod receiver; +pub mod sender; + +#[cfg(test)] +mod tests; + +#[derive(Clone, RuntimeDebug, Encode, Decode, PartialEq, Eq, TypeInfo, PartialOrd, Ord)] +pub enum KeyRelationship { + Encryption, + Verification(DidVerificationKeyRelationship), +} + +impl From for KeyRelationship { + fn from(value: DidVerificationKeyRelationship) -> Self { + Self::Verification(value) + } +} + +#[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] +pub struct KeyReferenceKey(pub KeyId, pub KeyRelationship); +#[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] +pub struct KeyReferenceValue; + +#[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] +pub struct KeyDetailsKey(pub KeyId); +#[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] +pub struct KeyDetailsValue(pub DidPublicKeyDetails); + +#[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, TypeInfo)] +pub enum ProofLeaf { + // The key and value for the leaves of a merkle proof that contain a reference + // (by ID) to the key details, provided in a separate leaf. + KeyReference(KeyReferenceKey, KeyReferenceValue), + // The key and value for the leaves of a merkle proof that contain the actual + // details of a DID public key. The key is the ID of the key, and the value is its details, including creation + // block number. + KeyDetails(KeyDetailsKey, KeyDetailsValue), +} + +impl ProofLeaf +where + KeyId: Encode, + BlockNumber: Encode, +{ + pub(crate) fn encoded_key(&self) -> Vec { + match self { + ProofLeaf::KeyReference(key, _) => key.encode(), + ProofLeaf::KeyDetails(key, _) => key.encode(), + } + } + + pub(crate) fn encoded_value(&self) -> Vec { + match self { + ProofLeaf::KeyReference(_, value) => value.encode(), + ProofLeaf::KeyDetails(_, value) => value.encode(), + } + } +} diff --git a/runtimes/common/src/dip/receiver.rs b/runtimes/common/src/dip/receiver.rs new file mode 100644 index 000000000..f63470cd8 --- /dev/null +++ b/runtimes/common/src/dip/receiver.rs @@ -0,0 +1,118 @@ +// 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 did::did_details::DidPublicKeyDetails; +use dip_support::{v1, VersionedIdentityProof}; +use frame_support::RuntimeDebug; +use pallet_dip_receiver::traits::IdentityProofVerifier; +use parity_scale_codec::Encode; +use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData, vec::Vec}; +use sp_trie::{verify_trie_proof, LayoutV1}; + +use crate::dip::{sender, KeyDetailsKey, KeyDetailsValue, KeyReferenceKey, KeyRelationship, ProofLeaf}; + +// TODO: Avoid repetition of the same key if it appears multiple times, e.g., by +// having a vector of `KeyRelationship` instead. +#[derive(RuntimeDebug, PartialEq, Eq)] +pub struct ProofEntry { + pub key: DidPublicKeyDetails, + pub relationship: KeyRelationship, +} + +// Contains the list of revealed public keys after a given merkle proof has been +// correctly verified. +#[derive(RuntimeDebug, PartialEq, Eq)] +pub struct VerificationResult(pub Vec>); + +impl From>> for VerificationResult { + fn from(value: Vec>) -> Self { + Self(value) + } +} + +pub struct DidMerkleProofVerifier(PhantomData<(KeyId, BlockNumber, Hasher)>); + +impl IdentityProofVerifier for DidMerkleProofVerifier +where + KeyId: Encode + Clone + Ord, + BlockNumber: Encode + Clone + Ord, + Hasher: sp_core::Hasher, +{ + type BlindedValue = Vec; + // TODO: Proper error handling + type Error = (); + type ProofDigest = ::Out; + type ProofLeaf = ProofLeaf; + type VerificationResult = VerificationResult; + + fn verify_proof_against_digest( + proof: VersionedIdentityProof, + digest: Self::ProofDigest, + ) -> Result { + let proof: v1::Proof<_, _> = proof.try_into()?; + // TODO: more efficient by removing cloning and/or collecting. + // Did not find another way of mapping a Vec<(Vec, Vec)> to a + // Vec<(Vec, Option>)>. + let proof_leaves = proof + .revealed + .iter() + .map(|leaf| (leaf.encoded_key(), Some(leaf.encoded_value()))) + .collect::, Option>)>>(); + verify_trie_proof::, _, _, _>(&digest, &proof.blinded, &proof_leaves).map_err(|_| ())?; + + // At this point, we know the proof is valid. We just need to map the revealed + // leaves to something the consumer can easily operate on. + + // Create a map of the revealed public keys + //TODO: Avoid cloning, and use a map of references for the lookup + let public_keys: BTreeMap> = proof + .revealed + .clone() + .into_iter() + .filter_map(|leaf| { + if let ProofLeaf::KeyDetails(KeyDetailsKey(key_id), KeyDetailsValue(key_details)) = leaf { + Some((key_id, key_details)) + } else { + None + } + }) + .collect(); + // Create a list of the revealed keys by consuming the provided key reference + // leaves, and looking up the full details from the just-built `public_keys` + // map. + let keys: Vec> = proof + .revealed + .into_iter() + .filter_map(|leaf| { + if let ProofLeaf::KeyReference(KeyReferenceKey(key_id, key_relationship), _) = leaf { + // TODO: Better error handling. + let key_details = public_keys + .get(&key_id) + .expect("Key ID should be present in the map of revealed public keys."); + Some(ProofEntry { + key: key_details.clone(), + relationship: key_relationship, + }) + } else { + None + } + }) + .collect(); + Ok(keys.into()) + } +} diff --git a/runtimes/common/src/dip/sender.rs b/runtimes/common/src/dip/sender.rs new file mode 100644 index 000000000..8ae4658ce --- /dev/null +++ b/runtimes/common/src/dip/sender.rs @@ -0,0 +1,221 @@ +// 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 did::{did_details::DidDetails, DidVerificationKeyRelationship, KeyIdOf}; +use dip_support::latest::Proof; +use frame_support::RuntimeDebug; +use pallet_dip_sender::traits::{IdentityProofGenerator, IdentityProvider}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_std::{borrow::ToOwned, collections::btree_set::BTreeSet, marker::PhantomData, vec::Vec}; +use sp_trie::{generate_trie_proof, LayoutV1, MemoryDB, TrieDBMutBuilder, TrieHash, TrieMut}; + +use crate::dip::{KeyDetailsKey, KeyDetailsValue, KeyReferenceKey, KeyReferenceValue, KeyRelationship, ProofLeaf}; + +pub type BlindedValue = Vec; + +pub type DidMerkleProof = Proof, ProofLeaf, ::BlockNumber>>; + +#[derive(Encode, Decode, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub struct CompleteMerkleProof { + pub root: Root, + pub proof: Proof, +} + +pub struct DidMerkleRootGenerator(PhantomData); + +impl DidMerkleRootGenerator +where + T: did::Config, +{ + // Calls the function in the `sp_trie` crate to generate the merkle root given + // the provided `DidDetails`. + // Each key in the merkle tree is added in the following way: + // - keys in the `public_keys` map are added by value in the merkle tree, with + // the leaf key being the key ID and the value being the key details + // - keys everywhere else in the DidDetails are added by reference, with the + // leaf key being the encoding of the tuple (keyID, key relationship) and the + // value being hte empty tuple + // A valid proof will contain a leaf with the key details for each reference + // leaf, with multiple reference leaves potentially referring to the same + // details leaf, as we already do with out `DidDetails` type. + fn calculate_root_with_db(identity: &DidDetails, db: &mut MemoryDB) -> Result { + let mut trie = TrieHash::>::default(); + let mut trie_builder = TrieDBMutBuilder::>::new(db, &mut trie).build(); + + // Authentication key + let auth_leaf = ProofLeaf::<_, T::BlockNumber>::KeyReference( + KeyReferenceKey( + identity.authentication_key, + DidVerificationKeyRelationship::Authentication.into(), + ), + KeyReferenceValue, + ); + trie_builder + .insert(auth_leaf.encoded_key().as_slice(), auth_leaf.encoded_value().as_slice()) + .map_err(|_| ())?; + // Attestation key, if present + if let Some(att_key_id) = identity.attestation_key { + let att_leaf = ProofLeaf::<_, T::BlockNumber>::KeyReference( + KeyReferenceKey(att_key_id, DidVerificationKeyRelationship::AssertionMethod.into()), + KeyReferenceValue, + ); + trie_builder + .insert(att_leaf.encoded_key().as_slice(), att_leaf.encoded_value().as_slice()) + .map_err(|_| ())?; + }; + // Delegation key, if present + if let Some(del_key_id) = identity.delegation_key { + let del_leaf = ProofLeaf::<_, T::BlockNumber>::KeyReference( + KeyReferenceKey(del_key_id, DidVerificationKeyRelationship::CapabilityDelegation.into()), + KeyReferenceValue, + ); + trie_builder + .insert(del_leaf.encoded_key().as_slice(), del_leaf.encoded_value().as_slice()) + .map_err(|_| ())?; + }; + // Key agreement keys + identity + .key_agreement_keys + .iter() + .try_for_each(|id| -> Result<(), ()> { + let enc_leaf = ProofLeaf::<_, T::BlockNumber>::KeyReference( + KeyReferenceKey(*id, KeyRelationship::Encryption), + KeyReferenceValue, + ); + trie_builder + .insert(enc_leaf.encoded_key().as_slice(), enc_leaf.encoded_value().as_slice()) + .map_err(|_| ())?; + Ok(()) + })?; + // Public keys + identity + .public_keys + .iter() + .try_for_each(|(id, key_details)| -> Result<(), ()> { + let key_leaf = ProofLeaf::KeyDetails(KeyDetailsKey(*id), KeyDetailsValue(key_details.clone())); + trie_builder + .insert(key_leaf.encoded_key().as_slice(), key_leaf.encoded_value().as_slice()) + .map_err(|_| ())?; + Ok(()) + })?; + trie_builder.commit(); + Ok(trie_builder.root().to_owned()) + } + + // TODO: Better error handling + // Only used for testing and as part of the features exposed by the runtime API + // of the provider. Given the provided `DidDetails` and a list of key IDs, it + // generates a merkle proof which only reveals the details of the provided key + // IDs. + #[allow(clippy::result_unit_err)] + pub fn generate_proof<'a, K>( + identity: &DidDetails, + mut key_ids: K, + ) -> Result>, ()> + where + K: Iterator>, + { + let mut db = MemoryDB::default(); + let root = Self::calculate_root_with_db(identity, &mut db)?; + + #[allow(clippy::type_complexity)] + let leaves: BTreeSet, T::BlockNumber>> = + key_ids.try_fold(BTreeSet::new(), |mut set, key_id| -> Result<_, ()> { + let key_details = identity.public_keys.get(key_id).ok_or(())?; + // Adds a key reference leaf for each relationship the key ID is part of. + if *key_id == identity.authentication_key { + set.insert(ProofLeaf::KeyReference( + KeyReferenceKey(*key_id, DidVerificationKeyRelationship::Authentication.into()), + KeyReferenceValue, + )); + } + if Some(*key_id) == identity.attestation_key { + set.insert(ProofLeaf::KeyReference( + KeyReferenceKey(*key_id, DidVerificationKeyRelationship::AssertionMethod.into()), + KeyReferenceValue, + )); + } + if Some(*key_id) == identity.delegation_key { + set.insert(ProofLeaf::KeyReference( + KeyReferenceKey(*key_id, DidVerificationKeyRelationship::CapabilityDelegation.into()), + KeyReferenceValue, + )); + } + if identity.key_agreement_keys.contains(key_id) { + set.insert(ProofLeaf::KeyReference( + KeyReferenceKey(*key_id, KeyRelationship::Encryption), + KeyReferenceValue, + )); + }; + // Then adds the actual key details to the merkle proof. + // If the same key is specified twice, the old key is simply replaced with a new + // key of the same value. + let key_details_leaf = + ProofLeaf::KeyDetails(KeyDetailsKey(*key_id), KeyDetailsValue(key_details.clone())); + if !set.contains(&key_details_leaf) { + set.insert(key_details_leaf); + } + Ok(set) + })?; + let encoded_keys: Vec> = leaves.iter().map(|l| l.encoded_key()).collect(); + let proof = generate_trie_proof::, _, _, _>(&db, root, &encoded_keys).map_err(|_| ())?; + Ok(CompleteMerkleProof { + root, + proof: DidMerkleProof:: { + blinded: proof, + revealed: leaves.into_iter().collect::>(), + }, + }) + } +} + +impl IdentityProofGenerator> for DidMerkleRootGenerator +where + T: did::Config, +{ + // TODO: Proper error handling + type Error = (); + type Output = T::Hash; + + fn generate_commitment(_identifier: &T::DidIdentifier, identity: &DidDetails) -> Result { + let mut db = MemoryDB::default(); + Self::calculate_root_with_db(identity, &mut db) + } +} + +pub struct DidIdentityProvider(PhantomData); + +impl IdentityProvider, ()> for DidIdentityProvider +where + T: did::Config, +{ + // TODO: Proper error handling + type Error = (); + + fn retrieve(identifier: &T::DidIdentifier) -> Result, ())>, Self::Error> { + match ( + did::Pallet::::get_did(identifier), + did::Pallet::::get_deleted_did(identifier), + ) { + (Some(details), _) => Ok(Some((details, ()))), + (_, Some(_)) => Ok(None), + _ => Err(()), + } + } +} diff --git a/runtimes/common/src/dip/tests.rs b/runtimes/common/src/dip/tests.rs new file mode 100644 index 000000000..ae88fb736 --- /dev/null +++ b/runtimes/common/src/dip/tests.rs @@ -0,0 +1,315 @@ +// 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 did::{ + did_details::{DidCreationDetails, DidEncryptionKey}, + DidVerificationKeyRelationship, KeyIdOf, +}; +use dip_support::latest::Proof; +use frame_support::{ + assert_err, assert_ok, construct_runtime, parameter_types, traits::Everything, weights::constants::RocksDbWeight, +}; +use frame_system::{ + mocking::{MockBlock, MockUncheckedExtrinsic}, + EnsureSigned, RawOrigin, +}; +use pallet_dip_receiver::traits::IdentityProofVerifier; +use parity_scale_codec::Encode; +use sp_core::{ecdsa, ed25519, sr25519, ConstU16, ConstU32, ConstU64, Hasher, Pair}; +use sp_io::TestExternalities; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentifyAccount, IdentityLookup}, + AccountId32, +}; +use sp_std::collections::btree_set::BTreeSet; + +use crate::dip::{ + receiver::DidMerkleProofVerifier, + sender::{CompleteMerkleProof, DidMerkleRootGenerator}, + ProofLeaf, +}; + +pub(crate) type AccountId = AccountId32; +pub(crate) type Balance = u128; +pub(crate) type Block = MockBlock; +pub(crate) type BlockNumber = u64; +pub(crate) type Hashing = BlakeTwo256; +pub(crate) type Index = u64; +pub(crate) type UncheckedExtrinsic = MockUncheckedExtrinsic; + +construct_runtime!( + pub enum TestRuntime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Balances: pallet_balances, + Did: did, + } +); + +impl frame_system::Config for TestRuntime { + type AccountData = pallet_balances::AccountData; + type AccountId = AccountId; + type BaseCallFilter = Everything; + type BlockHashCount = ConstU64<250>; + type BlockLength = (); + type BlockNumber = BlockNumber; + type BlockWeights = (); + type DbWeight = RocksDbWeight; + type Hash = ::Out; + type Hashing = Hashing; + type Header = Header; + type Index = Index; + type Lookup = IdentityLookup; + type MaxConsumers = ConstU32<16>; + type OnKilledAccount = (); + type OnNewAccount = (); + type OnSetCode = (); + type PalletInfo = PalletInfo; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type SS58Prefix = ConstU16<38>; + type SystemWeightInfo = (); + type Version = (); +} + +parameter_types! { + pub ExistentialDeposit: Balance = 500u64.into(); +} + +impl pallet_balances::Config for TestRuntime { + type AccountStore = System; + type Balance = Balance; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type MaxLocks = ConstU32<50>; + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = [u8; 8]; + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); +} + +parameter_types! { + pub Deposit: Balance = 500u64.into(); + pub Fee: Balance = 500u64.into(); + pub MaxBlocksTxValidity: BlockNumber = 10u64; + #[derive(Debug, Clone, Eq, PartialEq)] + pub const MaxTotalKeyAgreementKeys: u32 = 2; +} + +impl did::DeriveDidCallAuthorizationVerificationKeyRelationship for RuntimeCall { + fn derive_verification_key_relationship(&self) -> did::DeriveDidCallKeyRelationshipResult { + Ok(DidVerificationKeyRelationship::Authentication) + } + + #[cfg(feature = "runtime-benchmarks")] + fn get_call_for_did_call_benchmark() -> Self { + RuntimeCall::System(frame_system::Call::remark { remark: vec![] }) + } +} + +impl did::Config for TestRuntime { + type Currency = Balances; + type Deposit = Deposit; + type DidIdentifier = AccountId; + type EnsureOrigin = EnsureSigned; + type Fee = Fee; + type FeeCollector = (); + type MaxBlocksTxValidity = MaxBlocksTxValidity; + type MaxNewKeyAgreementKeys = ConstU32<2>; + type MaxNumberOfServicesPerDid = ConstU32<1>; + type MaxNumberOfTypesPerService = ConstU32<1>; + type MaxNumberOfUrlsPerService = ConstU32<1>; + type MaxPublicKeysPerDid = ConstU32<5>; + type MaxServiceIdLength = ConstU32<100>; + type MaxServiceTypeLength = ConstU32<100>; + type MaxServiceUrlLength = ConstU32<100>; + type MaxTotalKeyAgreementKeys = MaxTotalKeyAgreementKeys; + type OriginSuccess = AccountId; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type WeightInfo = (); +} + +fn base_ext() -> TestExternalities { + TestExternalities::new( + frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(), + ) +} + +const ALICE: AccountId = AccountId::new([1u8; 32]); + +#[test] +fn minimal_did_merkle_proof() { + base_ext().execute_with(|| { + // Give Alice some balance + assert_ok!(Balances::set_balance(RawOrigin::Root.into(), ALICE, 1_000_000_000, 0)); + // Generate a DID for alice + let did_auth_key = ed25519::Pair::from_seed(&[100u8; 32]); + let did: AccountId = did_auth_key.public().into_account().into(); + let create_details = DidCreationDetails { + did: did.clone(), + submitter: ALICE, + new_attestation_key: None, + new_delegation_key: None, + new_key_agreement_keys: BTreeSet::new().try_into().unwrap(), + new_service_details: vec![], + }; + // Create Alice's DID with only authentication key + assert_ok!(Did::create( + RawOrigin::Signed(ALICE).into(), + Box::new(create_details.clone()), + did_auth_key.sign(&create_details.encode()).into() + )); + let did_details = Did::get_did(&did).expect("DID should be present"); + + // 1. Create the DID merkle proof revealing only the authentication key + let CompleteMerkleProof { root, proof } = DidMerkleRootGenerator::::generate_proof( + &did_details, + [did_details.authentication_key].iter(), + ) + .expect("Merkle proof generation should not fail."); + println!("{:?} - {:?} - {:?} bytes", root, proof, proof.encoded_size()); + // Verify the generated merkle proof + assert_ok!( + DidMerkleProofVerifier::, BlockNumber, Hashing>::verify_proof_against_digest( + proof.clone().into(), + root + ) + ); + + // 2. Fail to generate a Merkle proof for a key that does not exist + assert_err!( + DidMerkleRootGenerator::::generate_proof( + &did_details, + [<::Out>::default()].iter() + ), + () + ); + + // 3. Fail to verify a merkle proof with a compromised merkle root + let new_root = <::Out>::default(); + assert_err!( + DidMerkleProofVerifier::, BlockNumber, Hashing>::verify_proof_against_digest( + proof.into(), + new_root + ), + () + ); + }) +} + +#[test] +fn complete_did_merkle_proof() { + base_ext().execute_with(|| { + // Give Alice some balance + assert_ok!(Balances::set_balance(RawOrigin::Root.into(), ALICE, 1_000_000_000, 0)); + // Generate a DID for alice + let did_auth_key = ed25519::Pair::from_seed(&[100u8; 32]); + let did_att_key = sr25519::Pair::from_seed(&[150u8; 32]); + let did_del_key = ecdsa::Pair::from_seed(&[200u8; 32]); + let enc_keys = BTreeSet::from_iter(vec![ + DidEncryptionKey::X25519([250u8; 32]), + DidEncryptionKey::X25519([251u8; 32]), + ]); + let did: AccountId = did_auth_key.public().into_account().into(); + let create_details = DidCreationDetails { + did: did.clone(), + submitter: ALICE, + new_attestation_key: Some(did_att_key.public().into()), + new_delegation_key: Some(did_del_key.public().into()), + new_key_agreement_keys: enc_keys + .try_into() + .expect("BTreeSet to BoundedBTreeSet should not fail"), + new_service_details: vec![], + }; + // Create Alice's DID with only authentication key + assert_ok!(Did::create( + RawOrigin::Signed(ALICE).into(), + Box::new(create_details.clone()), + did_auth_key.sign(&create_details.encode()).into() + )); + let did_details = Did::get_did(&did).expect("DID should be present"); + + // 1. Create the DID merkle proof revealing only the authentication key + let CompleteMerkleProof { root, proof } = DidMerkleRootGenerator::::generate_proof( + &did_details, + [did_details.authentication_key].iter(), + ) + .expect("Merkle proof generation should not fail."); + // Verify the generated merkle proof + assert_ok!( + DidMerkleProofVerifier::, BlockNumber, Hashing>::verify_proof_against_digest( + proof.into(), + root + ) + ); + + // 2. Create the DID merkle proof revealing all the keys + let CompleteMerkleProof { root, proof } = DidMerkleRootGenerator::::generate_proof( + &did_details, + [ + did_details.authentication_key, + did_details.attestation_key.unwrap(), + did_details.delegation_key.unwrap(), + ] + .iter() + .chain(did_details.key_agreement_keys.iter()), + ) + .expect("Merkle proof generation should not fail."); + // Verify the generated merkle proof + assert_ok!( + DidMerkleProofVerifier::, BlockNumber, Hashing>::verify_proof_against_digest( + proof.into(), + root + ) + ); + + // 2. Create the DID merkle proof revealing only the key reference and not the + // key ID + let CompleteMerkleProof { root, proof } = DidMerkleRootGenerator::::generate_proof( + &did_details, + [did_details.authentication_key].iter(), + ) + .expect("Merkle proof generation should not fail."); + let reference_only_authentication_leaf: Vec<_> = proof + .revealed + .into_iter() + .filter(|l| !matches!(l, ProofLeaf::KeyDetails(_, _))) + .collect(); + // Fail to verify the generated merkle proof + assert_err!( + DidMerkleProofVerifier::, BlockNumber, Hashing>::verify_proof_against_digest( + Proof { + blinded: proof.blinded, + revealed: reference_only_authentication_leaf + } + .into(), + root + ), + () + ); + }) +} diff --git a/runtimes/common/src/lib.rs b/runtimes/common/src/lib.rs index d627b067a..e94d361b4 100644 --- a/runtimes/common/src/lib.rs +++ b/runtimes/common/src/lib.rs @@ -43,6 +43,7 @@ use sp_std::marker::PhantomData; pub mod assets; pub mod authorization; pub mod constants; +pub mod dip; pub mod errors; pub mod fees; pub mod migrations;