metadata_shortener/
lib.rs

1//! This crate is a shortener and digest generation tool for Substrate chains
2//! metadata.
3//!
4//! # Shortened metadata
5//!
6//! During chain data parsing, only small fraction of the chain metadata is
7//! actually utilized.
8//!
9//! Hardware signer devices with limited memory capability can encounter
10//! difficulties receiving and processing whole metadata which size is
11//! typically a few hundred kB.
12//! Receiving and using only the part required for decoding of particular data
13//! piece greatly simplifies the task, as the typical metadata part size
14//! decreases down to few kB.
15//!
16//! Decoding of the signable transaction, or extrinsic, requires both
17//! information on extrinsic structure and the description of corresponding
18//! types. Signable transaction is built as a SCALE-encoded call and
19//! SCALE-encoded extensions concatenated to it. Call may or may not be double
20//! SCALE-encoded, i.e. preceded by [compact](parity_scale_codec::Compact) of
21//! the call length.
22//!
23//! Type describing all calls available is `call_ty` field in
24//! [`ExtrinsicMetadata`](https://docs.rs/frame-metadata/latest/frame_metadata/v15/struct.ExtrinsicMetadata.html).
25//! The extensions set is determined by `signed_extensions` in
26//! `ExtrinsicMetadata`.
27//!
28//! `ShortMetadata` contains:
29//!
30//! - short types registry [`ShortRegistry`] with description of all types
31//! needed for signable transaction decoding (both for
32//! call and for extensions),
33//! - data from missing types, sufficient for Merkle tree root hash calculation
34//! (part of digest calculation, see below),
35//! - [`MetadataDescriptor`] with other relatively short data necessary for
36//! decoding and appropriate data representation
37//!
38//! Note: chain specs (except base58 prefix in some cases) are a part of
39//! `MetadataDescriptor`, but are **not** in the full metadata, and should be
40//! fetched from chain and provided separately on `ShortMetadata` generation
41//! step, as [`ShortSpecs`].
42//!
43//! `ShortRegistry` is generated on the hot side, as the the transaction is
44//! preliminarily decoded and the types used are collected. Entries in
45//! `ShortRegistry` are [`PortableType`](scale_info::PortableType) values with
46//! unique `id` (same as in [`PortableRegistry`](scale_info::PortableRegistry))
47//! for type resolving and [`Type`](scale_info::Type) itself. For enums only the
48//! variants used in actual decoding are retained, all enum variants remain
49//! within a single entry.
50//!
51//! `ShortMetadata` is generated with
52//! `cut_metadata` function for transactions with
53//! double SCALE-encoded call part (length-prefixed), and with
54//! `cut_metadata_transaction_unmarked` function for single SCALE-encoded call
55//! part.
56//!
57//! `ShortMetadata` implements trait
58//! [`AsMetadata`](substrate_parser::AsMetadata) and could be used for chain
59//! data decoding using tools of [`substrate_parser`] crate.
60//!
61//! SCALE-encoded `ShortMetadata` structure (as received by the cold side) is
62//! following:
63//!
64//! - `ShortRegistry`:
65//!   - [Compact](parity_scale_codec::Compact) of the number of types described
66//! in `ShortRegistry`
67//!   - For each of the given number of types:
68//!     - compact type `id` (same number as in original full metadata, for type
69//! resolving)
70//!     - SCALE-encoded [`Type`](scale_info::Type), encoded size is not known
71//! before decoding
72//! - Indices for Merkle tree leaves derived from types in `ShortRegistry`:
73//!   - Compact of the number of indices for Merkle tree leaves derived from
74//! types in `ShortRegistry`
75//!   - Given number of SCALE-encoded `u32` indices, 4 bytes each
76//! - Merkle tree lemmas:
77//!   - Compact of the number of lemmas for Merkle tree
78//!   - Given number of lemmas, 32 bytes each
79//! - SCALE-encoded [`MetadataDescriptor`]:
80//!   - 1-byte version of [`MetadataDescriptor`] (currently the only functioning
81//! variant is `1`). For version `1`:
82//!     - `id` in types registry for the type describing all available calls
83//!     - Signed extensions set:
84//!         - Compact of the number of provided [`SignedExtensionMetadata`]
85//! entries
86//!         - Given number of SCALE-encoded `SignedExtensionMetadata`, encoded
87//! size of each is not known before decoding
88//!     - Compact length of the printed spec version followed by corresponding
89//! number of utf8 bytes
90//!     - Compact length of the chain spec name followed by corresponding number
91//! of utf8 bytes
92//!     - SCALE-encoded `u16` base58 prefix value for the chain, 2 bytes
93//!     - SCALE-encoded `u8` decimals value for the chain, 1 byte
94//!     - Compact length of the unit value for the chain followed by
95//! corresponding number of utf8 bytes
96//!
97//! # Example
98//! ```
99//! # #[cfg(all(feature = "std", feature = "merkle-standard", feature = "proof-gen"))]
100//! # {
101//! use frame_metadata::v15::RuntimeMetadataV15;
102//! use metadata_shortener::{
103//!     traits::{Blake3Leaf, ExtendedMetadata},
104//!     cut_metadata, ShortMetadata, ShortSpecs,
105//! };
106//! use parity_scale_codec::{Decode, Encode};
107//! use primitive_types::H256;
108//! use std::str::FromStr;
109//! use substrate_parser::{parse_transaction, AsMetadata};
110//!
111//! // Hex metadata string, read from file.
112//! let meta_hex = std::fs::read_to_string("for_tests/westend1006001").unwrap();
113//! let meta = hex::decode(meta_hex.trim()).unwrap();
114//!
115//! // Full metadata is quite bulky. Check SCALE-encoded size here, for simplicity:
116//! assert_eq!(291897, meta.len());
117//!
118//! // Full `RuntimeMetadataV15`, ready to use.
119//! let full_metadata = RuntimeMetadataV15::decode(&mut &meta[5..]).unwrap();
120//!
121//! let specs_westend = ShortSpecs {
122//!     base58prefix: 42,
123//!     decimals: 12,
124//!     unit: "WND".to_string(),
125//! };
126//!
127//! // Transaction for which the metadata is cut: utility batch call combining
128//! // two staking calls.
129//! let data = hex::decode("c901100208060007001b2c3ef70006050c0008264834504a64ace1373f0c8ed5d57381ddf54a2f67a318fa42b1352681606d00aebb0211dbb07b4d335a657257b8ac5e53794c901e4f616d4a254f2490c43934009ae581fef1fc06828723715731adcf810e42ce4dadad629b1b7fa5c3c144a81d55000800b1590f0007000000e143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e5b1d91c89d3de85a4d6eee76ecf3a303cf38b59e7d81522eb7cd24b02eb161ff").unwrap();
130//!
131//! // Make short metadata here. It is sufficient to decode the transaction.
132//! let short_metadata =
133//!     cut_metadata(&data.as_ref(), &mut (), &full_metadata, &specs_westend).unwrap();
134//!
135//! // `ShortMetadata` is substantially shorter. SCALE-encoded size:
136//! assert_eq!(4486, short_metadata.encode().len());
137//!
138//! // Now check that decoding result remains unchanged.
139//!
140//! // Transaction parsed with shortened metadata, carded:
141//! let parsed_with_short_meta = parse_transaction(
142//!     &data.as_ref(),
143//!     &mut (),
144//!     &short_metadata,
145//!     None,
146//! )
147//! .unwrap()
148//! .card(
149//!     &<ShortMetadata<Blake3Leaf, ()> as ExtendedMetadata<()>>::to_specs(&short_metadata)
150//!         .unwrap(),
151//!     &<ShortMetadata<Blake3Leaf, ()> as AsMetadata<()>>::spec_name_version(&short_metadata)
152//!         .unwrap()
153//!         .spec_name,
154//! );
155//!
156//! // Transaction parsed with full metadata, carded:
157//! let parsed_with_full_meta = parse_transaction(
158//!     &data.as_ref(),
159//!     &mut (),
160//!     &full_metadata,
161//!     None,
162//! )
163//! .unwrap()
164//! .card(
165//!     &specs_westend,
166//!     &<RuntimeMetadataV15 as AsMetadata<()>>::spec_name_version(&full_metadata)
167//!         .unwrap()
168//!         .spec_name,
169//! );
170//!
171//! // Call parsing result for short metadata (printed cards, without documentation):
172//! let call_printed_short_meta = parsed_with_short_meta
173//!     .call_result
174//!     .unwrap()
175//!     .iter()
176//!     .map(|card| card.show())
177//!     .collect::<Vec<String>>()
178//!     .join("\n");
179//!
180//! // Call parsing result for full metadata (printed cards, without documentation):
181//! let call_printed_full_meta = parsed_with_full_meta
182//!     .call_result
183//!     .unwrap()
184//!     .iter()
185//!     .map(|card| card.show())
186//!     .collect::<Vec<String>>()
187//!     .join("\n");
188//!
189//! // Call parsing results did not change.
190//! assert_eq!(call_printed_short_meta, call_printed_full_meta);
191//!
192//! // Extensions parsing result for short metadata (printed cards, without documentation):
193//! let extensions_printed_short_meta = parsed_with_short_meta
194//!     .extensions
195//!     .iter()
196//!     .map(|card| card.show())
197//!     .collect::<Vec<String>>()
198//!     .join("\n");
199//!
200//! // Extensions parsing result for short metadata (printed cards, without documentation):
201//! let extensions_printed_full_meta = parsed_with_full_meta
202//!     .extensions
203//!     .iter()
204//!     .map(|card| card.show())
205//!     .collect::<Vec<String>>()
206//!     .join("\n");
207//!
208//! // Extensions parsing results did not change.
209//! assert_eq!(extensions_printed_short_meta, extensions_printed_full_meta);
210//! # }
211//! ```
212//!
213//! # Metadata digest
214//!
215//! The decoding of the chain data is beneficial from safety viewpoint only if
216//! the metadata can be guaranteed to be authentic.
217//! A possible solution to that would be to produce a digest of the metadata and
218//! concat it to the signable transaction prior to signing, so that the
219//! signature would be valid only if the metadata used for decoding matches the
220//! one on chain. This crate generates such digest, both for complete and for
221//! shortened metadata.
222//!
223//! Digest is generated by merging the root hash of the Merkle tree build over
224//! metadata's [`PortableRegistry`](scale_info::PortableRegistry) with the hash
225//! of SCALE-encoded [`MetadataDescriptor`].
226//!
227//! ## Merkle tree for types data
228//!
229//! Merkle tree is generated and processed using tools of
230//! [`merkle_cbt`](https://docs.rs/merkle-cbt/latest/merkle_cbt/) and
231//! [`merkle_cbt_lean`](https://docs.rs/merkle-cbt-lean/latest/merkle_cbt_lean/)
232//! crates. While providing the same outcome, `merkle_cbt_lean` is tailored for
233//! `no_std` environments with low internal memory capacity and external (streamed) data.
234//!
235//! Merkle leaves are blake3-hashed SCALE-encoded individual
236//! [`PortableType`](scale_info::PortableType) values. In enums the same `id` is
237//! used for every retained variant, and every retained variant is placed as an
238//! individual enum with a single variant.
239//!
240//! For full metadata
241//! [`RuntimeMetadataV15`](https://docs.rs/frame-metadata/latest/frame_metadata/v15/struct.RuntimeMetadataV15.html),
242//! all leaves are constructed, deterministically sorted, and processed to build
243//! the Merkle tree, and then the root hash.
244//! In `ShortMetadata`, the available types data is transformed into leaves
245//! and combined with `MerkleProof` to calculate the root hash.
246//!
247//! Trait [`HashableRegistry`](crate::traits::HashableRegistry) for producing
248//! sorted set of Merkle tree leaves is implemented for
249//! [`PortableRegistry`](scale_info::PortableRegistry) and for
250//! [`ShortRegistry`].
251//!
252//! Trait `HashableMetadata` for producing Merkle tree root hash is implemented
253//! both for
254//! [`RuntimeMetadataV15`](https://docs.rs/frame-metadata/latest/frame_metadata/v15/struct.RuntimeMetadataV15.html)
255//! and for `ShortMetadata`. Complete digest could be calculated for
256//! `HashableMetadata` if `ShortSpecs` are provided.
257//!
258//! `ShortMetadata` also implements trait `ExtendedMetadata` for digest
259//! calculation and transaction parsing without providing additional data.
260//!
261//! ## Metadata descriptor
262//!
263//! `MetadataDescriptor` contains other relatively short data necessary for
264//! decoding and appropriate data representation:
265//!
266//! - `id` in types registry for the type describing all available calls
267//! - set of signed extension metadata entries [`SignedExtensionMetadata`]
268//! - chain spec name and spec version (extracted from `Version` constant of the
269//! `System` pallet)
270//! - chain specs (base58 prefix for in-chain Ss58 address representation,
271//! decimals and unit for balance values representation)
272//!
273//! `MetadataDescriptor` is versioned to simplify version compatibility check on
274//! the hardware side.
275//!
276//! # Example
277//! ```
278//! # #[cfg(all(feature = "std", feature = "merkle-standard", feature = "proof-gen"))]
279//! # {
280//! use frame_metadata::v15::RuntimeMetadataV15;
281//! use metadata_shortener::{
282//!     cut_metadata,
283//!     traits::{Blake3Leaf, ExtendedMetadata, HashableMetadata},
284//!     MetadataDescriptor, ShortMetadata, ShortSpecs,
285//! };
286//! use parity_scale_codec::Decode;
287//! use substrate_parser::AsMetadata;
288//!
289//! // Hex metadata string, read from file.
290//! let meta_hex = std::fs::read_to_string("for_tests/westend1006001").unwrap();
291//! let meta = hex::decode(meta_hex.trim()).unwrap();
292//!
293//! // Full `RuntimeMetadataV15`, ready to use.
294//! let full_metadata = RuntimeMetadataV15::decode(&mut &meta[5..]).unwrap();
295//!
296//! let specs_westend = ShortSpecs {
297//!     base58prefix: 42,
298//!     decimals: 12,
299//!     unit: "WND".to_string(),
300//! };
301//!
302//! // Full metadata digest:
303//! let digest_full_metadata =
304//!     <RuntimeMetadataV15 as HashableMetadata<()>>::digest_with_short_specs(
305//!         &full_metadata,
306//!         &specs_westend,
307//!         &mut (),
308//!     )
309//!     .unwrap();
310//!
311//! // Same transaction as in above example.
312//! let data = hex::decode("c901100208060007001b2c3ef70006050c0008264834504a64ace1373f0c8ed5d57381ddf54a2f67a318fa42b1352681606d00aebb0211dbb07b4d335a657257b8ac5e53794c901e4f616d4a254f2490c43934009ae581fef1fc06828723715731adcf810e42ce4dadad629b1b7fa5c3c144a81d55000800d624000007000000e143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e5b1d91c89d3de85a4d6eee76ecf3a303cf38b59e7d81522eb7cd24b02eb161ff").unwrap();
313//!
314//! // Generate short metadata:
315//! let short_metadata =
316//!     cut_metadata(&data.as_ref(), &mut (), &full_metadata, &specs_westend).unwrap();
317//!
318//! // Short metadata digest:
319//! let digest_short_metadata =
320//!     <ShortMetadata<Blake3Leaf, ()> as ExtendedMetadata<()>>::digest(
321//!         &short_metadata,
322//!         &mut ()
323//!     ).unwrap();
324//!
325//! // Check that digest values match:
326//! assert_eq!(digest_short_metadata, digest_full_metadata);
327//! # }
328//! ```
329//!
330//! # RuntimeMetadata versions support
331//!
332//! [`RuntimeMetadataV14`](https://docs.rs/frame-metadata/latest/frame_metadata/v14/struct.RuntimeMetadataV14.html)
333//! implements trait `AsMetadata` and could be used for transactions decoding.
334//!
335//! Trait `HashableMetadata` could be implemented for `RuntimeMetadataV14` (and,
336//! in fact, was, in earlier editions of this crate), but intentionally is not.
337//!
338//! The types registry of `RuntimeMetadataV14` has structure similar to that of
339//! `RuntimeMetadataV15`, however, the types in registries for same
340//! `spec_version` are different in `V14` and `V15`, with `RuntimeMetadataV14`
341//! having types not available in `RuntimeMetadataV15` and vise versa, thus
342//! making it not feasible to support both simultaneously during the
343//! transitioning phase.
344//!
345//! V15 and above are expected to be supported.
346//!
347//! # Available features
348//!
349//! - `merkle-standard`: for calculating `RuntimeMetadataV15` digest using tools
350//! of [`merkle_cbt`](https://docs.rs/merkle-cbt/latest/merkle_cbt/)
351//! crate. Intended for signature checking side. Digest is constant while
352//! metadata `spec_version` remains the same.
353//!
354//! - `merkle-lean`: for calculating `ShortMetadata` digest on cold signer side
355//! using tools of
356//! [`merkle_cbt_lean`](https://docs.rs/merkle-cbt-lean/latest/merkle_cbt_lean/)
357//! crate.
358//!
359//! - `proof-gen`: for generating `ShortMetadata` on wallet side, using tools of
360//! [`merkle_cbt_lean`](https://docs.rs/merkle-cbt-lean/latest/merkle_cbt_lean/)
361//! crate. `proof-gen` feature includes `merkle-lean`.
362//!
363//! - `std`
364//!
365//! By default, all features are made available.
366//!
367#![no_std]
368#![deny(unused_crate_dependencies)]
369
370pub mod cutter;
371pub mod error;
372#[cfg(test)]
373#[cfg(any(feature = "merkle-standard", feature = "merkle-lean", test))]
374mod tests;
375pub mod traits;
376
377#[cfg(any(feature = "std", test))]
378#[macro_use]
379extern crate std;
380
381#[cfg(all(not(feature = "std"), not(test)))]
382#[macro_use]
383extern crate alloc as std;
384
385#[cfg(feature = "merkle-lean")]
386pub use crate::cutter::ShortMetadata;
387#[cfg(feature = "proof-gen")]
388pub use crate::cutter::{cut_metadata, cut_metadata_transaction_unmarked};
389pub use crate::cutter::{MetadataDescriptor, ShortRegistry};
390
391pub use substrate_parser::{
392    traits::{SignedExtensionMetadata, SpecNameVersion},
393    ShortSpecs,
394};