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}