/__w/smoldot/smoldot/repo/lib/src/verify/aura.rs
Line | Count | Source (jump to first uncovered line) |
1 | | // Smoldot |
2 | | // Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. |
3 | | // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 |
4 | | |
5 | | // This program is free software: you can redistribute it and/or modify |
6 | | // it under the terms of the GNU General Public License as published by |
7 | | // the Free Software Foundation, either version 3 of the License, or |
8 | | // (at your option) any later version. |
9 | | |
10 | | // This program is distributed in the hope that it will be useful, |
11 | | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | | // GNU General Public License for more details. |
14 | | |
15 | | // You should have received a copy of the GNU General Public License |
16 | | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
17 | | |
18 | | //! AURA consensus. |
19 | | //! |
20 | | //! AURA, for Authority Round, is one of the consensus algorithm available to Substrate-based |
21 | | //! chains in order to determine who is authorized to generate a block. |
22 | | //! |
23 | | //! Every block (with the exception of the genesis block) must contain, in its header, some data |
24 | | //! that makes it possible to verify that it has been generated by a legitimate author. |
25 | | //! |
26 | | //! # Overview of AURA |
27 | | //! |
28 | | //! In the AURA algorithm, time is divided into non-overlapping **slots**. How long a slot is |
29 | | //! never changes, and is determined by calling the `AuraApi_slot_duration` runtime entry point, |
30 | | //! typically at initialization. The current slot number is equal to `unix_time / slot_duration`. |
31 | | //! The slot number of a block can be found in its header. |
32 | | //! |
33 | | //! > **Note**: Slot durations values are usually around 3 to 20 seconds. |
34 | | //! |
35 | | //! A list of authorities (each authority being represented as a public key) is maintained by the |
36 | | //! chain. The current authorities of a block can be found by calling the `AuraApi_authorities` |
37 | | //! runtime entry point. |
38 | | //! When this list is modified, a consensus log item is added to the header of the block, meaning |
39 | | //! that this runtime entry point doesn't have to be called every single time. |
40 | | //! |
41 | | //! During a slot, only the authority whose public key is found at |
42 | | //! `authorities[slot_number % authorities.len()]` is allowed to produce a block. |
43 | | //! |
44 | | //! Each block being produced must include an Aura seal in its header containing a signature of |
45 | | //! the block header (with the exclusion of the seal itself) made using the public key in question. |
46 | | //! |
47 | | |
48 | | use crate::header; |
49 | | |
50 | | use alloc::vec::Vec; |
51 | | use core::{num::NonZeroU64, time::Duration}; |
52 | | |
53 | | /// Configuration for [`verify_header`]. |
54 | | pub struct VerifyConfig<'a, TAuthList> { |
55 | | /// Header of the block to verify. |
56 | | pub header: header::HeaderRef<'a>, |
57 | | |
58 | | /// Number of bytes used to encode the block number in the header. |
59 | | pub block_number_bytes: usize, |
60 | | |
61 | | /// Header of the parent of the block to verify. |
62 | | /// |
63 | | /// [`verify_header`] assumes that this block has been successfully verified before. |
64 | | /// |
65 | | /// The hash of this header must be the one referenced in [`VerifyConfig::header`]. |
66 | | pub parent_block_header: header::HeaderRef<'a>, |
67 | | |
68 | | /// Time elapsed since [the Unix Epoch](https://en.wikipedia.org/wiki/Unix_time) (i.e. |
69 | | /// 00:00:00 UTC on 1 January 1970), ignoring leap seconds. |
70 | | pub now_from_unix_epoch: Duration, |
71 | | |
72 | | /// Aura authorities that must validate the block. |
73 | | /// |
74 | | /// This list is either equal to the parent's list, or, if the parent changes the list of |
75 | | /// authorities, equal to that new modified list. |
76 | | pub current_authorities: TAuthList, |
77 | | |
78 | | /// Duration of a slot in milliseconds. |
79 | | /// Can be found by calling the `AuraApi_slot_duration` runtime function. |
80 | | pub slot_duration: NonZeroU64, |
81 | | } |
82 | | |
83 | | /// Information yielded back after successfully verifying a block. |
84 | | #[derive(Debug)] |
85 | | pub struct VerifySuccess { |
86 | | /// `Some` if the list of authorities is modified by this block. Contains the new list of |
87 | | /// authorities. |
88 | | pub authorities_change: Option<Vec<header::AuraAuthority>>, |
89 | | } |
90 | | |
91 | | /// Failure to verify a block. |
92 | 0 | #[derive(Debug, derive_more::Display)] Unexecuted instantiation: _RNvXs0_NtNtCsN16ciHI6Qf_7smoldot6verify4auraNtB5_11VerifyErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt Unexecuted instantiation: _RNvXs0_NtNtCseuYC0Zibziv_7smoldot6verify4auraNtB5_11VerifyErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt |
93 | | pub enum VerifyError { |
94 | | /// The seal (containing the signature of the authority) is missing from the header. |
95 | | MissingSeal, |
96 | | /// No pre-runtime digest in the block header. |
97 | | MissingPreRuntimeDigest, |
98 | | /// Parent block doesn't contain any Aura information. |
99 | | ParentIsntAuraConsensus, |
100 | | /// Slot number must be strictly increasing between a parent and its child. |
101 | | SlotNumberNotIncreasing, |
102 | | /// Slot number starts too far in the future. |
103 | | TooFarInFuture, |
104 | | /// Block header signature is invalid. |
105 | | BadSignature, |
106 | | /// Failed to parse Ed25519 public key. |
107 | | BadPublicKey, |
108 | | /// List of authorities is empty. |
109 | | EmptyAuthorities, |
110 | | /// Header contains more than one new list of authorities. |
111 | | MultipleAuthoritiesChangeDigestItems, |
112 | | } |
113 | | |
114 | | /// Verifies whether a block header provides a correct proof of the legitimacy of the authorship. |
115 | 0 | pub fn verify_header<'a>( |
116 | 0 | mut config: VerifyConfig<'a, impl ExactSizeIterator<Item = header::AuraAuthorityRef<'a>>>, |
117 | 0 | ) -> Result<VerifySuccess, VerifyError> { |
118 | | // TODO: handle OnDisabled |
119 | | |
120 | | // Gather the slot number from the header. |
121 | 0 | let slot_number = config |
122 | 0 | .header |
123 | 0 | .digest |
124 | 0 | .aura_pre_runtime() |
125 | 0 | .ok_or(VerifyError::MissingPreRuntimeDigest)? |
126 | | .slot_number; |
127 | | |
128 | | // Make sure that the slot number is strictly increasing between a parent and its children. |
129 | 0 | if config.parent_block_header.number != 0 { |
130 | 0 | let parent_slot_number = match config.parent_block_header.digest.aura_pre_runtime() { |
131 | 0 | Some(pr) => pr.slot_number, |
132 | 0 | None => return Err(VerifyError::ParentIsntAuraConsensus), |
133 | | }; |
134 | | |
135 | 0 | if slot_number <= parent_slot_number { |
136 | 0 | return Err(VerifyError::SlotNumberNotIncreasing); |
137 | 0 | } |
138 | 0 | } |
139 | | |
140 | | // Check that the slot number isn't a slot in the future. |
141 | | // Since there might be a clock drift (either locally or on the authority that created the |
142 | | // block), a tolerance period is added. |
143 | | // If the local node is an authority itself, and the best block uses a slot number `N` seconds |
144 | | // in the future, then for the next `N` seconds the local node won't produce any block. As |
145 | | // such, a high tolerance level constitutes an attack vector. |
146 | | { |
147 | | const TOLERANCE: Duration = Duration::from_secs(30); |
148 | 0 | let current_slot = |
149 | 0 | (config.now_from_unix_epoch + TOLERANCE).as_secs() * 1000 / config.slot_duration.get(); |
150 | 0 | if slot_number > current_slot { |
151 | 0 | return Err(VerifyError::TooFarInFuture); |
152 | 0 | } |
153 | 0 | }; |
154 | 0 |
|
155 | 0 | // Check whether there is an authority change in the block. |
156 | 0 | // This information is used in case of success. |
157 | 0 | let mut authorities_change = None; |
158 | 0 | for digest_item in config.header.digest.logs() { |
159 | | if let header::DigestItemRef::AuraConsensus( |
160 | 0 | header::AuraConsensusLogRef::AuthoritiesChange(new_list), |
161 | 0 | ) = digest_item |
162 | | { |
163 | 0 | if authorities_change.is_some() { |
164 | 0 | return Err(VerifyError::MultipleAuthoritiesChangeDigestItems); |
165 | 0 | } |
166 | 0 |
|
167 | 0 | authorities_change = Some(new_list.map(Into::into).collect()); |
168 | | // We continue looping even after finding a list, to make sure there is only one list. |
169 | 0 | } |
170 | | } |
171 | | |
172 | | // The signature in the seal applies to the header from where the signature isn't present. |
173 | | // Extract the signature and build the hash that is expected to be signed. |
174 | 0 | let (seal_signature, pre_seal_hash) = { |
175 | 0 | let mut unsealed_header = config.header; |
176 | 0 | let seal_signature = match unsealed_header.digest.pop_seal() { |
177 | 0 | Some(header::Seal::Aura(seal)) => { |
178 | 0 | schnorrkel::Signature::from_bytes(seal).map_err(|_| VerifyError::BadSignature)? Unexecuted instantiation: _RNCINvNtNtCsN16ciHI6Qf_7smoldot6verify4aura13verify_headerNtNtNtB8_6header4aura19AuraAuthoritiesIterE0B8_ Unexecuted instantiation: _RNCINvNtNtCseuYC0Zibziv_7smoldot6verify4aura13verify_headerNtNtNtB8_6header4aura19AuraAuthoritiesIterE0B8_ |
179 | | } |
180 | 0 | _ => return Err(VerifyError::MissingSeal), |
181 | | }; |
182 | 0 | ( |
183 | 0 | seal_signature, |
184 | 0 | unsealed_header.hash(config.block_number_bytes), |
185 | 0 | ) |
186 | 0 | }; |
187 | 0 |
|
188 | 0 | // Fetch the authority that has supposedly signed the block. |
189 | 0 | if config.current_authorities.len() == 0 { |
190 | | // Checked beforehand in order to not do a modulo 0 operation. |
191 | 0 | return Err(VerifyError::EmptyAuthorities); |
192 | 0 | } |
193 | 0 | // About overflows: |
194 | 0 | // If `config.current_authorities.len()` doesn't fit in a `u64`, then |
195 | 0 | // `slot_number % config.current_authorities.len()` is necessarily equal to `slot_number`. |
196 | 0 | // Since we're using a modulo operation, `signing_authority` is always inferior to |
197 | 0 | // `current_authorities.len()`, meaning that it always fits in a `usize`. |
198 | 0 | let signing_authority = usize::try_from( |
199 | 0 | slot_number % u64::try_from(config.current_authorities.len()).unwrap_or(u64::MAX), |
200 | 0 | ) |
201 | 0 | .unwrap_or_else(|_| unreachable!()); Unexecuted instantiation: _RNCINvNtNtCsN16ciHI6Qf_7smoldot6verify4aura13verify_headerpEs_0B8_ Unexecuted instantiation: _RNCINvNtNtCseuYC0Zibziv_7smoldot6verify4aura13verify_headerpEs_0B8_ |
202 | 0 |
|
203 | 0 | // This `unwrap()` can only panic if `public_key` is the wrong length, which we know can't |
204 | 0 | // happen as it's of type `[u8; 32]`. |
205 | 0 | let authority_public_key = schnorrkel::PublicKey::from_bytes( |
206 | 0 | config |
207 | 0 | .current_authorities |
208 | 0 | .nth(signing_authority) |
209 | 0 | .unwrap() |
210 | 0 | .public_key, |
211 | 0 | ) |
212 | 0 | .unwrap(); |
213 | 0 |
|
214 | 0 | // Now verifying the signature in the seal. |
215 | 0 | authority_public_key |
216 | 0 | .verify_simple(b"substrate", &pre_seal_hash, &seal_signature) |
217 | 0 | .map_err(|_| VerifyError::BadSignature)?; Unexecuted instantiation: _RNCINvNtNtCsN16ciHI6Qf_7smoldot6verify4aura13verify_headerNtNtNtB8_6header4aura19AuraAuthoritiesIterEs0_0B8_ Unexecuted instantiation: _RNCINvNtNtCseuYC0Zibziv_7smoldot6verify4aura13verify_headerNtNtNtB8_6header4aura19AuraAuthoritiesIterEs0_0B8_ |
218 | | |
219 | | // Success! 🚀 |
220 | 0 | Ok(VerifySuccess { authorities_change }) |
221 | 0 | } Unexecuted instantiation: _RINvNtNtCsN16ciHI6Qf_7smoldot6verify4aura13verify_headerNtNtNtB6_6header4aura19AuraAuthoritiesIterEB6_ Unexecuted instantiation: _RINvNtNtCseuYC0Zibziv_7smoldot6verify4aura13verify_headerNtNtNtB6_6header4aura19AuraAuthoritiesIterEB6_ |