Coverage Report

Created: 2024-05-16 12:16

/__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_