frame_suite/
keys.rs

1// SPDX-License-Identifier: MPL-2.0
2//
3// Part of Auguth Labs open-source softwares.
4// Built for the Substrate framework.
5//
6// This Source Code Form is subject to the terms of the Mozilla Public
7// License, v. 2.0. If a copy of the MPL was not distributed with this
8// file, You can obtain one at https://mozilla.org/MPL/2.0/.
9//
10// Copyright (c) 2026 Auguth Labs (OPC) Pvt Ltd, India
11
12// ===============================================================================
13// ````````````````````````````````` KEYS SUITE ``````````````````````````````````
14// ===============================================================================
15
16//! Utilities for deterministic identifier derivation.
17//!
18//! Provides a generic mechanism to generate reproducible identifiers (`Id`)
19//! from a combination of:
20//! - a base key (`Id`)
21//! - an associated item (`Item`)
22//! - a salt (`Salt`)
23//!
24//! using a hashing algorithm (via [`Hash`]).
25//!
26//! The same input tuple `(Id, Item, Salt)` will always produce the same output,
27//! enabling stable and namespaced key derivation.
28//!
29//! ## Components
30//!
31//! - [`KeySeedFor`]: Encodes the derivation inputs and produces a derived key.
32//! - [`KeyGenFor`]: Trait providing a generic interface for key generation.
33//!
34//! ## Guarantees
35//!
36//! - Deterministic: identical inputs yield identical outputs
37//! - Domain separation via `(Item, Salt)` inputs
38//!
39//! ## Notes
40//!
41//! - Uniqueness depends on correct salt usage
42//! - Decoding from hash output must succeed for valid key generation
43
44// ===============================================================================
45// ``````````````````````````````````` IMPORTS ```````````````````````````````````
46// ===============================================================================
47
48// --- Core ---
49use core::marker::PhantomData;
50
51// --- SCALE & metadata ---
52use codec::{Decode, Encode, FullCodec, MaxEncodedLen};
53use scale_info::TypeInfo;
54
55// --- Substrate crates ---
56use sp_runtime::{traits::Hash, RuntimeDebug};
57
58// ===============================================================================
59// ``````````````````````````````` STRUCTURES ````````````````````````````````````
60// ===============================================================================
61
62/// A seed structure for deterministic identifier derivation.
63///
64/// `KeySeedFor` enables the generation of a unique, deterministic identifier (`Id`)
65/// from a combination of:
66/// - a target key type (`Id`)
67/// - a meta-data value (`Item`)
68/// - a unique salt (`Salt`)
69///
70/// within the context of a specific runtime (`T`) and hashing algorithm (`Hash`).
71///
72/// ### Overview
73/// This struct serves as the canonical pre-image for generating new deterministic
74/// IDs, based on a source identity (`target`), a value of interest (`item`), and a
75/// unique `salt`.
76///
77/// ### Type Parameters
78/// - `Item`: The value associated with the derived identifier. May be low entropy
79/// or even default.
80/// - `Id`: The identifier type, used both as the source (`key`) and the derived
81/// output (`target`).
82/// - `Salt`: A unique value to ensure uniqueness per `(Id, Item)` pair.
83/// - `Hash`: The hashing algorithm used for deterministic ID derivation.
84/// - `T`: The runtime context.
85///
86/// ### Constraints & Responsibilities
87/// - `target` (`Id`): Must be a high-entropy, globally unique identifier (e.g., account ID, hash, public key).
88/// - `item` (`Item`): May be low-entropy; a single `Id` can be associated with multiple `Item` types,
89///   but each `Item` type must be uniquely tied to a single `Id` type.
90/// - `salt`: Must be unique per `(Id, Item)` pair; implementers must ensure salts are not reused.
91///
92/// ### Example Use Cases
93/// - Sub-identities derived from a parent identity
94/// - Capability delegation
95/// - Namespaced object IDs
96/// - Resource handles scoped to specific keys
97#[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)]
98#[scale_info(skip_type_params(T))]
99pub struct KeySeedFor<Id, Item, Salt, Hash, T> {
100    target: Id,
101    item: Item,
102    salt: Salt,
103
104    #[codec(skip)]
105    #[scale_info(skip)]
106    _hash: PhantomData<Hash>,
107
108    #[codec(skip)]
109    #[scale_info(skip)]
110    _marker: PhantomData<T>,
111}
112
113// ===============================================================================
114// ````````````````````````````` INHERENT IMPLS ``````````````````````````````````
115// ===============================================================================
116
117/// Implementation of utility methods for `KeySeedFor`.
118impl<Id, Item, Salt, Hasher, T> KeySeedFor<Id, Item, Salt, Hasher, T>
119where
120    Id: Clone + FullCodec,
121    Item: Clone + FullCodec,
122    Salt: Clone + FullCodec,
123    Hasher: Hash,
124{
125    /// Gets the current value of the target key.
126    ///
127    pub fn target(&self) -> Id {
128        self.target.clone()
129    }
130
131    /// Gets the current value of the item (metadata for the target key).
132    ///
133    pub fn item(&self) -> Item {
134        self.item.clone()
135    }
136
137    /// Gets the current value of the salt used for key derivation.
138    ///
139    pub fn salt(&self) -> Salt {
140        self.salt.clone()
141    }
142
143    /// Provides mutable access to the target key.
144    ///
145    pub fn mut_target(&mut self) -> &mut Id {
146        &mut self.target
147    }
148
149    /// Provides mutable access to the item (metadata).
150    ///
151    pub fn mut_item(&mut self) -> &mut Item {
152        &mut self.item
153    }
154
155    /// Provides mutable access to the salt.
156    ///
157    pub fn mut_salt(&mut self) -> &mut Salt {
158        &mut self.salt
159    }
160
161    /// Constructs a new `KeySeedFor` from the given target, item and salt.
162    ///
163    pub fn new(target: Id, item: Item, salt: Salt) -> Self {
164        Self {
165            target,
166            item,
167            salt,
168            _marker: PhantomData,
169            _hash: PhantomData,
170        }
171    }
172
173    /// Generates a deterministic identifier (`Id`) by hashing the encoded
174    /// target, item, and salt.
175    ///
176    /// Returns `Some(Id)` if decoding from the hash output succeeds, or
177    /// `None` otherwise.
178    pub fn key_gen(&self) -> Option<Id> {
179        let mut encoded = self.item().encode();
180        encoded.extend(self.target().encode());
181        encoded.extend(self.salt().encode());
182
183        let hash = <Hasher as Hash>::hash(&encoded);
184        let Ok(id) = Id::decode(&mut hash.as_ref()) else {
185            return None;
186        };
187        Some(id)
188    }
189}
190
191// ===============================================================================
192// ````````````````````````````````` TRAITS ``````````````````````````````````````
193// ===============================================================================
194
195/// Trait for generating deterministic identifiers from a combination of key, item,
196/// and salt.
197///
198/// This trait abstracts the process of deriving a unique, reproducible identifier (`Id`)
199/// from a source key (`target`), associated item (metadata), and a salt value, using
200/// a specified hashing algorithm.
201///
202/// It is intended for use cases where deterministic, namespaced, or context-specific
203/// IDs are required, such as sub-identities, capability delegation, or resource handles.
204///
205/// ## Type Parameters
206/// - `Id`: The identifier type to be generated and used as the source key.
207/// - `Item`: The metadata or context value associated with the identifier.
208/// - `Salt`: A unique value to ensure uniqueness per `(Id, Item)` pair.
209/// - `Hasher`: The hashing algorithm used for deterministic ID derivation.
210/// - `T`: The runtime context (e.g., a Substrate pallet's `Config`).
211pub trait KeyGenFor<Id, Item, Salt, Hasher, T>
212where
213    Id: Clone + FullCodec,
214    Item: Clone + FullCodec,
215    Salt: Clone + FullCodec,
216    Hasher: Hash,
217{
218    /// Generates a deterministic identifier (`Id`) from the given target, item,
219    /// and salt.
220    ///
221    /// This method constructs a [`KeySeedFor`] instance and invokes its `key_gen`
222    /// method, ensuring that the same input combination always produces the same
223    /// output identifier.
224    ///
225    /// Returns `Some(Id)` if key generation succeeds, or `None` if decoding from
226    /// the hash output fails.
227    fn gen_key(target: &Id, item: &Item, salt: Salt) -> Option<Id> {
228        let key = KeySeedFor::<Id, Item, Salt, Hasher, T>::new(target.clone(), item.clone(), salt)
229            .key_gen()?;
230        Some(key)
231    }
232}
233
234/// Blanket implementation of [`KeyGenFor`] for [`KeySeedFor`].
235///
236/// This allows any `KeySeedFor` instance to use the `gen_key` utility
237///
238impl<Id, Item, Salt, Hasher, T> KeyGenFor<Id, Item, Salt, Hasher, T>
239    for KeySeedFor<Id, Item, Salt, Hasher, T>
240where
241    Id: Clone + FullCodec,
242    Item: Clone + FullCodec,
243    Salt: Clone + FullCodec,
244    Hasher: Hash,
245{
246}
247
248// ===============================================================================
249// ```````````````````````````` TEST UTILITIES ```````````````````````````````````
250// ===============================================================================
251
252/// Internal test utilities for validating [`KeyGenFor`] implementations.
253///
254/// This module provides reusable checks to ensure deterministic behavior
255/// and basic input separation properties of key generation logic.
256pub mod test_utils {
257    use super::*;
258
259    /// Verifies deterministic key generation.
260    ///
261    /// Ensures that identical `(target, item, salt)` inputs produce
262    /// the same derived key across multiple invocations.
263    pub fn run_keygen_deterministic_check<
264        Id: Clone + FullCodec + PartialEq + core::fmt::Debug,
265        Item: Clone + FullCodec,
266        Salt: Clone + FullCodec,
267        Hasher: Hash,
268        T,
269        Impl: KeyGenFor<Id, Item, Salt, Hasher, T>,
270    >(
271        target: Id,
272        item: Item,
273        salt: Salt,
274    ) {
275        // Determinism
276        let gen_key_1 = Impl::gen_key(&target, &item, salt.clone()).unwrap();
277        let gen_key_2 = Impl::gen_key(&target, &item, salt.clone()).unwrap();
278        assert_eq!(gen_key_1, gen_key_2);
279    }
280
281    /// Verifies input sensitivity of key generation.
282    ///
283    /// Ensures that changes in any of the inputs (`target`, `item`, or `salt`)
284    /// result in a different derived key.
285    pub fn run_keygen_collision_check<
286        Id: Clone + FullCodec + PartialEq + core::fmt::Debug,
287        Item: Clone + FullCodec + PartialEq + core::fmt::Debug,
288        Salt: Clone + FullCodec + PartialEq + core::fmt::Debug,
289        Hasher: Hash,
290        T,
291        Impl: KeyGenFor<Id, Item, Salt, Hasher, T>,
292    >(
293        target: Id,
294        item: Item,
295        salt: Salt,
296        dif_target: Id,
297        dif_item: Item,
298        dif_salt: Salt,
299    ) {
300        let base_key = Impl::gen_key(&target, &item, salt.clone()).unwrap();
301        let dif_salt = Impl::gen_key(&target, &item, dif_salt).unwrap();
302        let dif_item = Impl::gen_key(&target, &dif_item, salt.clone()).unwrap();
303        let dif_target = Impl::gen_key(&dif_target, &item, salt.clone()).unwrap();
304
305        // asserts
306        assert_ne!(base_key, dif_salt);
307        assert_ne!(base_key, dif_item);
308        assert_ne!(base_key, dif_target);
309    }
310}