trait_keyless/
keyless.rs

1use crate::types::*;
2use parity_scale_codec::alloc::string::ToString;
3use scale_info::prelude::string::String;
4
5// Original implementation of "blake2" and "blake2_256":
6// https://github.com/paritytech/polkadot-sdk/blob/polkadot-stable2407-2/substrate/primitives/crypto/hashing/src/lib.rs
7
8#[inline(always)]
9fn blake2<const N: usize>(data: &[u8]) -> [u8; N] {
10    blake2b_simd::Params::new()
11        .hash_length(N)
12        .hash(data)
13        .as_bytes()
14        .try_into()
15        .expect("slice is always the necessary length")
16}
17
18/// Do a Blake2 256-bit hash and return result.
19pub(crate) fn blake2_256(data: &[u8]) -> [u8; 32] {
20    blake2(data)
21}
22
23/// Builds a keyless account by concatenating open part of the account with the checksum.
24///
25/// # Arguments
26///
27/// `open_part` - The open part of the account.
28///
29/// # Returns
30///
31/// Returns the built keyless account as an `AccountId32`
32fn _encode_account(open_part: &[u8]) -> AccountId32
33where
34    AccountId32: From<[u8; 32]>,
35{
36    let mut checksum = blake2_256(open_part);
37    checksum[0..open_part.len()].clone_from_slice(open_part);
38    return AccountId32::from(checksum);
39}
40
41/// Builds a keyless account for the specified `AppAgentId`.
42///
43/// ********************************
44/// The structure of an AppAgent account:
45/// [0..=3] -> AppAgentId
46/// [4..4] -> Address type identifier (1)
47/// [5..=31] -> Checksum
48/// ********************************
49///
50/// # Arguments
51///
52/// * `app_agent_id` - The `AppAgentId` used to build the keyless account.
53///
54/// # Returns
55///
56/// Returns the built keyless account as an `AccountId32`
57pub fn encode_app_agent_account(app_agent_id: &AppAgentId) -> AccountId32
58where
59    AccountId32: From<[u8; 32]>,
60{
61    let mut open_part: [u8; 5] = [0u8; 5];
62    open_part[0..4].copy_from_slice(&app_agent_id.to_le_bytes());
63    open_part[4] = APP_AGENT_ADDRESS_IDENTIFIER;
64
65    return _encode_account(&open_part);
66}
67
68/// Builds a keyless account for the specified `AppAgentId` and `TransactionalId`.
69///
70/// ********************************
71/// The structure of a Transactional account:
72/// [0..=3] -> AppAgentId
73/// [4..4] -> Address type identifier (2)
74/// [5..=8] -> TransactionalId
75/// [9..=31] -> Checksum
76/// ********************************
77///
78/// # Arguments
79///
80/// * `app_agent_id` - The `AppAgentId` used to build the keyless account.
81/// * `ta_id` - The `TransactionalId` used to build the keyless account.
82///
83/// # Returns
84///
85/// Returns the built keyless account as an `AccountId32`
86pub fn encode_transactional_account(
87    app_agent_id: &AppAgentId,
88    ta_id: &TransactionalId,
89) -> AccountId32
90where
91    AppAgentId: Into<u32>,
92    TransactionalId: Into<u32>,
93    AccountId32: From<[u8; 32]>,
94{
95    let mut open_part: [u8; 9] = [0u8; 9];
96    open_part[0..4].copy_from_slice(&app_agent_id.to_le_bytes());
97    open_part[4] = TRANSACTIONAL_ADDRESS_IDENTIFIER;
98    open_part[5..9].copy_from_slice(&ta_id.to_le_bytes());
99
100    return _encode_account(&open_part);
101}
102
103/// Builds a keyless account for the specified `AppAgentId` and `Name`.
104///
105/// ********************************
106/// The structure of a Named account:
107/// [0..=3] -> AppAgentId
108/// [4..4] -> Address type identifier (3)
109/// [5..=14] -> Address name
110/// [15..=31] -> Checksum
111/// ********************************
112///
113/// # Arguments
114///
115/// * `app_agent_id` - The `AppAgentId` used to build the keyless account.
116/// * `name` - The `AccountId32` used to build the keyless account.
117///
118/// # Returns
119///
120/// Returns a `Result` containing the built keyless account as an `AccountId32` if successful,
121/// or a `String` if an error occurs during the building process.
122pub fn encode_named_account(app_agent_id: &AppAgentId, name: &AddressName) -> AccountId32
123where
124    AppAgentId: Into<u32>,
125    AccountId32: From<[u8; 32]>,
126{
127    let mut open_part: [u8; 15] = [0u8; 15];
128    open_part[0..4].copy_from_slice(&app_agent_id.to_le_bytes());
129    open_part[4] = NAMED_ADDRESS_IDENTIFIER;
130    open_part[5..15].copy_from_slice(name.as_bytes());
131
132    return _encode_account(&open_part);
133}
134
135/// Decodes and verifies the correctness of a keyless account.
136/// Provides type of account and keyless IDs.
137fn decode_account_ids(account: &AccountId32) -> BlockchainAccountIds
138where
139    AccountId32: From<[u8; 32]>,
140{
141    let account_bytes: &[u8; 32] = account.as_ref();
142
143    let type_identifier: AddressIdentifierType = account_bytes[4];
144    match type_identifier {
145        APP_AGENT_ADDRESS_IDENTIFIER => {
146            // Verify checksum
147            let open_part: &[u8; 5] = &account_bytes[0..5].try_into().unwrap();
148            let checksum: &[u8; 27] = &account_bytes[5..32].try_into().unwrap();
149            let calculated_checksum: [u8; 32] = blake2_256(open_part);
150            let calculated_checksum_trimmed: &[u8; 27] =
151                (&calculated_checksum[5..32]).try_into().unwrap();
152
153            if checksum == calculated_checksum_trimmed {
154                // Extract and decode IDs
155                let app_agent_id_bytes = account_bytes[0..4].try_into().unwrap();
156                let app_agent_id: AppAgentId = u32::from_le_bytes(app_agent_id_bytes);
157
158                return BlockchainAccountIds::AppAgent(app_agent_id);
159            };
160        }
161        TRANSACTIONAL_ADDRESS_IDENTIFIER => {
162            // Verify checksum
163            let open_part: &[u8; 9] = &account_bytes[0..9].try_into().unwrap();
164            let checksum: &[u8; 23] = &account_bytes[9..32].try_into().unwrap();
165            let calculated_checksum: [u8; 32] = blake2_256(open_part);
166            let calculated_checksum_trimmed: &[u8; 23] =
167                (&calculated_checksum[9..32]).try_into().unwrap();
168
169            if checksum == calculated_checksum_trimmed {
170                // Extract and decode IDs
171                let app_agent_id_bytes = account_bytes[0..4].try_into().unwrap();
172                let app_agent_id: AppAgentId = u32::from_le_bytes(app_agent_id_bytes);
173                // 5 = appagent_id_size + address_identifier_size
174                // 9 = appagent_id_size + address_identifier_size + ta_id_size
175                let ta_id_bytes = account_bytes[5..9].try_into().unwrap();
176                let ta_id: TransactionalId = u32::from_le_bytes(ta_id_bytes);
177
178                return BlockchainAccountIds::Transactional((app_agent_id, ta_id));
179            };
180        }
181        NAMED_ADDRESS_IDENTIFIER => {
182            // Verify checksum
183            let open_part: &[u8; 15] = &account_bytes[0..15].try_into().unwrap();
184            let checksum: &[u8; 17] = &account_bytes[15..32].try_into().unwrap();
185            let calculated_checksum: [u8; 32] = blake2_256(open_part);
186            let calculated_checksum_trimmed: &[u8; 17] =
187                (&calculated_checksum[15..32]).try_into().unwrap();
188
189            if checksum == calculated_checksum_trimmed {
190                // Extract and decode IDs
191                let app_agent_id_bytes = account_bytes[..4].try_into().unwrap();
192                let app_agent_id: AppAgentId = u32::from_le_bytes(app_agent_id_bytes);
193                let address_name = account_bytes[5..15].try_into().unwrap();
194
195                return BlockchainAccountIds::Named((app_agent_id, address_name));
196            };
197        }
198        _ => {}
199    }
200
201    return BlockchainAccountIds::Regular;
202}
203
204/// Decodes and verifies the correctness of a keyless account.
205///
206/// # Arguments
207///
208/// * `account` - The keyless account to be decoded and verified.
209///
210/// # Returns
211///
212/// Returns a `Result` containing the decoded open part of the keyless account as a tuple of
213/// `(AddressType, AppAgentId, Option<TransactionalId>, Option<AddressName>)`
214/// if the decoding and verification are successful,
215/// or a `String` if an error occurs during decoding or verification.
216pub fn decode_account(account: AccountId32) -> BlockchainAddressInfo {
217    match decode_account_ids(&account) {
218        BlockchainAccountIds::Regular => BlockchainAddressInfo::from_regular_account(account),
219        BlockchainAccountIds::AppAgent(app_agent_id) => {
220            BlockchainAddressInfo::from_app_agent_account(account, app_agent_id)
221        }
222        BlockchainAccountIds::Transactional((app_agent_id, ta_id)) => {
223            BlockchainAddressInfo::from_ta_account(account, app_agent_id, ta_id)
224        }
225        BlockchainAccountIds::Named((app_agent_id, address_name)) => {
226            BlockchainAddressInfo::from_named_account(account, app_agent_id, address_name)
227        }
228    }
229}
230
231/// Decodes the provided keyless account and retrieves the corresponding `AppAgentId`.
232///
233/// # Arguments
234///
235/// * `account` - The keyless account to be decoded.
236///
237/// # Returns
238///
239/// Returns a `Result` containing the `AppAgentId` decoded from the keyless account if successful,
240/// or a `String` if an error occurs during the decoding process.
241pub fn decode_app_agent_account(account: &AccountId32) -> Result<AppAgentId, String> {
242    match decode_account_ids(account) {
243        BlockchainAccountIds::AppAgent(app_agent_id) => Ok(app_agent_id),
244        _ => Err("Provided account is not an AppAgent keyless account".to_string()),
245    }
246}
247
248/// Decodes the provided keyless account and retrieves the `AppAgentId` and
249/// `TransactionalId`.
250///
251/// # Arguments
252///
253/// * `account` - The keyless account to be decoded.
254///
255/// # Returns
256///
257/// Returns a `Result` containing the decoded `(AppAgentId, TransactionalId)` if successful,
258/// or a `String` if an error occurs during the decoding process.
259pub fn decode_transactional_account(
260    account: &AccountId32,
261) -> Result<(AppAgentId, TransactionalId), String> {
262    match decode_account_ids(account) {
263        BlockchainAccountIds::Transactional((app_agent_id, ta_id)) => Ok((app_agent_id, ta_id)),
264        _ => Err("Provided account is not a Transactional keyless account".to_string()),
265    }
266}
267
268/// Decodes the provided keyless account and retrieves the `AppAgentId` and
269/// `AddressName`.
270///
271/// # Arguments
272///
273/// * `account` - The keyless account to be decoded.
274///
275/// # Returns
276///
277/// Returns a `Result` containing the decoded `(AppAgentId, AddressName)` if successful,
278/// or a `String` if an error occurs during the decoding process.
279pub fn decode_named_account(account: &AccountId32) -> Result<(AppAgentId, AddressName), String> {
280    match decode_account_ids(account) {
281        BlockchainAccountIds::Named((app_agent_id, address_name)) => {
282            Ok((app_agent_id, address_name))
283        }
284        _ => Err("Provided account is not a Named keyless account".to_string()),
285    }
286}
287
288/// Checks if provided account is a keyless account.
289///
290/// # Arguments
291///
292/// * `account` - The keyless account to be checked.
293///
294/// # Returns
295///
296/// Returns a `true` if provided accouont is a keyless one, otherwise `false`.
297pub fn is_keyless_account(account: &AccountId32) -> bool {
298    match decode_account_ids(account) {
299        BlockchainAccountIds::Regular => false,
300        BlockchainAccountIds::AppAgent(_)
301        | BlockchainAccountIds::Transactional(_)
302        | BlockchainAccountIds::Named(_) => true,
303    }
304}