Coverage Report

Created: 2025-07-01 09:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/__w/smoldot/smoldot/repo/lib/src/identity/keystore.rs
Line
Count
Source
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
//! Data structure containing cryptographic key pairs.
19
//!
20
//! The keystore is a shared data structure (i.e. all of its functions accept `&self` rather than
21
//! `&mut self`, making it possible to share it through an `Arc` for example) containing a list of
22
//! cryptographic key pairs (i.e. both the public and secret keys).
23
//!
24
//! Each key pair contained within the keystore is identified as a `(KeyNamespace, [u8; 32])`
25
//! tuple, where the `[u8; 32]` is the public key. See [`KeyNamespace`].
26
//!
27
//! A keystore is optionally associated with a directory of the file system into which it will
28
//! store secret keys permanently. Keys present in this directory are considered to be the content
29
//! of the keystore data structure.
30
//!
31
//! For caching reasons, adding and removing keys to the directory manually (for example through
32
//! the [`std::fs`] API) doesn't automatically propagate to the public API of the keystore.
33
//! Similarly, it is not intended to be possible to create two [`Keystore`] instances associated
34
//! to the same directory at the same time.
35
//!
36
//! > **Note**: The Substrate framework also has a keystore, however this keystore implementation
37
//! >           isn't compatible with the Substrate keystore implementation. In other words, this
38
//! >           keystore cannot load keys found in a directory that was previously associated with
39
//! >           a Substrate keystore. This was decided because the Substrate keystore made some
40
//! >           questionable decisions that it has to keep for backwards compatibility reasons.
41
//! >           This keystore, being newly-written, doesn't have to follow them.
42
43
#![cfg(feature = "std")]
44
45
use crate::{identity::seed_phrase, util::SipHasherBuild};
46
47
use async_lock::Mutex;
48
use rand_chacha::rand_core::{RngCore as _, SeedableRng as _};
49
use std::{borrow::Cow, fs, io, path, str};
50
51
/// Namespace of the key.
52
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
53
// TODO: document
54
pub enum KeyNamespace {
55
    Aura,
56
    AuthorityDiscovery,
57
    Babe,
58
    Grandpa,
59
    ImOnline,
60
    // TODO: there exists other variants in Substrate but it's unclear whether they're in use (see https://github.com/paritytech/substrate/blob/cafe12e7785bf92e5dc04780c10e7f8330a15a4c/primitives/core/src/crypto.rs)
61
}
62
63
impl KeyNamespace {
64
    /// Returns all existing variants of [`KeyNamespace`].
65
0
    pub fn all() -> impl ExactSizeIterator<Item = KeyNamespace> {
66
0
        [
67
0
            KeyNamespace::Aura,
68
0
            KeyNamespace::AuthorityDiscovery,
69
0
            KeyNamespace::Babe,
70
0
            KeyNamespace::Grandpa,
71
0
            KeyNamespace::ImOnline,
72
0
        ]
73
0
        .into_iter()
74
0
    }
Unexecuted instantiation: _RNvMNtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB2_12KeyNamespace3all
Unexecuted instantiation: _RNvMNtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB2_12KeyNamespace3all
75
76
2
    fn from_string(str: &str) -> Option<Self> {
77
2
        match str {
78
2
            "aura" => 
Some(KeyNamespace::Aura)1
,
79
1
            "audi" => 
Some(KeyNamespace::AuthorityDiscovery)0
,
80
1
            "babe" => Some(KeyNamespace::Babe),
81
0
            "gran" => Some(KeyNamespace::Grandpa),
82
0
            "imon" => Some(KeyNamespace::ImOnline),
83
0
            _ => None,
84
        }
85
2
    }
_RNvMNtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB2_12KeyNamespace11from_string
Line
Count
Source
76
2
    fn from_string(str: &str) -> Option<Self> {
77
2
        match str {
78
2
            "aura" => 
Some(KeyNamespace::Aura)1
,
79
1
            "audi" => 
Some(KeyNamespace::AuthorityDiscovery)0
,
80
1
            "babe" => Some(KeyNamespace::Babe),
81
0
            "gran" => Some(KeyNamespace::Grandpa),
82
0
            "imon" => Some(KeyNamespace::ImOnline),
83
0
            _ => None,
84
        }
85
2
    }
Unexecuted instantiation: _RNvMNtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB2_12KeyNamespace11from_string
86
87
4
    fn as_string(&self) -> &'static str {
88
4
        match self {
89
2
            KeyNamespace::Aura => "aura",
90
0
            KeyNamespace::AuthorityDiscovery => "audi",
91
2
            KeyNamespace::Babe => "babe",
92
0
            KeyNamespace::Grandpa => "gran",
93
0
            KeyNamespace::ImOnline => "imon",
94
        }
95
4
    }
_RNvMNtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB2_12KeyNamespace9as_string
Line
Count
Source
87
4
    fn as_string(&self) -> &'static str {
88
4
        match self {
89
2
            KeyNamespace::Aura => "aura",
90
0
            KeyNamespace::AuthorityDiscovery => "audi",
91
2
            KeyNamespace::Babe => "babe",
92
0
            KeyNamespace::Grandpa => "gran",
93
0
            KeyNamespace::ImOnline => "imon",
94
        }
95
4
    }
Unexecuted instantiation: _RNvMNtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB2_12KeyNamespace9as_string
96
}
97
98
/// Collection of key pairs.
99
///
100
/// This module doesn't give you access to the content of private keys, only to signing
101
/// capabilities.
102
pub struct Keystore {
103
    keys_directory: Option<path::PathBuf>,
104
    guarded: Mutex<Guarded>,
105
    /// Cached base signing context cloned when signing with `sr25519`.
106
    sr25519_signing_context: schnorrkel::context::SigningContext,
107
}
108
109
impl Keystore {
110
    /// Initializes a new keystore.
111
    ///
112
    /// Must be passed bytes of entropy that are used to avoid hash collision attacks and to
113
    /// generate private keys.
114
    ///
115
    /// An error is returned if the `keys_directory` couldn't be opened because, for example, of
116
    /// some missing permission or because it isn't a directory.
117
    /// If the `keys_directory` doesn't exist, it will be created using `fs::create_dir_all`.
118
25
    pub async fn new(
119
25
        keys_directory: Option<path::PathBuf>,
120
25
        randomness_seed: [u8; 32],
121
25
    ) -> Result<Self, io::Error> {
_RNvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB4_8Keystore3new
Line
Count
Source
118
4
    pub async fn new(
119
4
        keys_directory: Option<path::PathBuf>,
120
4
        randomness_seed: [u8; 32],
121
4
    ) -> Result<Self, io::Error> {
_RNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB4_8Keystore3new
Line
Count
Source
118
21
    pub async fn new(
119
21
        keys_directory: Option<path::PathBuf>,
120
21
        randomness_seed: [u8; 32],
121
21
    ) -> Result<Self, io::Error> {
122
25
        let mut gen_rng = rand_chacha::ChaCha20Rng::from_seed(randomness_seed);
123
124
25
        let mut keys = hashbrown::HashMap::with_capacity_and_hasher(32, {
125
25
            SipHasherBuild::new({
126
25
                let mut seed = [0; 16];
127
25
                gen_rng.fill_bytes(&mut seed);
128
25
                seed
129
            })
130
        });
131
132
        // Load the keys from the disk.
133
        // TODO: return some diagnostic about invalid files?
134
25
        if let Some(
keys_directory4
) = &keys_directory {
135
4
            if !keys_directory.try_exists()
?0
{
136
0
                fs::create_dir_all(keys_directory)?;
137
4
            }
138
139
4
            for 
entry2
in fs::read_dir(keys_directory)
?0
{
140
2
                let entry = entry
?0
;
141
2
                if entry.file_type()
?0
.is_dir() {
142
0
                    continue;
143
2
                }
144
145
                // Try to match the file name.
146
2
                let file_name = match entry.file_name().into_string() {
147
2
                    Ok(n) => n,
148
0
                    Err(_) => continue,
149
                };
150
151
2
                let mut parser = nom::combinator::all_consuming::<
152
2
                    _,
153
2
                    (&str, nom::error::ErrorKind),
154
2
                    _,
155
2
                >(nom::combinator::complete((
156
2
                    nom::combinator::map_opt(
157
2
                        nom::bytes::streaming::take(4u32),
158
                        KeyNamespace::from_string,
159
                    ),
160
2
                    nom::bytes::streaming::tag("-"),
161
2
                    nom::combinator::map_opt(nom::bytes::streaming::take(7u32), |b| match b {
162
2
                        "ed25519" => 
Some(PrivateKey::FileEd25519)1
,
163
1
                        "sr25519" => Some(PrivateKey::FileSr25519),
164
0
                        _ => None,
165
2
                    }),
_RNCNCNvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB8_8Keystore3new00Bc_
Line
Count
Source
161
2
                    nom::combinator::map_opt(nom::bytes::streaming::take(7u32), |b| match b {
162
2
                        "ed25519" => 
Some(PrivateKey::FileEd25519)1
,
163
1
                        "sr25519" => Some(PrivateKey::FileSr25519),
164
0
                        _ => None,
165
2
                    }),
Unexecuted instantiation: _RNCNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB8_8Keystore3new00CscoAnRPySggw_6author
Unexecuted instantiation: _RNCNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB8_8Keystore3new00Bc_
Unexecuted instantiation: _RNCNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB8_8Keystore3new00CsjyNE3yDMkgA_14json_rpc_basic
Unexecuted instantiation: _RNCNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB8_8Keystore3new00Cs4VrkfB1pvQ3_25json_rpc_general_requests
166
2
                    nom::bytes::streaming::tag("-"),
167
2
                    nom::combinator::map_opt(
168
128
                        
nom::bytes::complete::take_while2
(|c: char| {
169
128
                            c.is_ascii_digit() || 
('a'..='f')47
.
contains47
(
&c47
)
170
128
                        }),
_RNCNCNvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB8_8Keystore3new0s_0Bc_
Line
Count
Source
168
128
                        nom::bytes::complete::take_while(|c: char| {
169
128
                            c.is_ascii_digit() || 
('a'..='f')47
.
contains47
(
&c47
)
170
128
                        }),
Unexecuted instantiation: _RNCNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB8_8Keystore3new0s_0CscoAnRPySggw_6author
Unexecuted instantiation: _RNCNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB8_8Keystore3new0s_0Bc_
Unexecuted instantiation: _RNCNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB8_8Keystore3new0s_0CsjyNE3yDMkgA_14json_rpc_basic
Unexecuted instantiation: _RNCNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB8_8Keystore3new0s_0Cs4VrkfB1pvQ3_25json_rpc_general_requests
171
2
                        |k: &str| {
172
2
                            if k.len() == 64 {
173
2
                                Some(<[u8; 32]>::try_from(hex::decode(k).unwrap()).unwrap())
174
                            } else {
175
0
                                None
176
                            }
177
2
                        },
_RNCNCNvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB8_8Keystore3new0s0_0Bc_
Line
Count
Source
171
2
                        |k: &str| {
172
2
                            if k.len() == 64 {
173
2
                                Some(<[u8; 32]>::try_from(hex::decode(k).unwrap()).unwrap())
174
                            } else {
175
0
                                None
176
                            }
177
2
                        },
Unexecuted instantiation: _RNCNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB8_8Keystore3new0s0_0CscoAnRPySggw_6author
Unexecuted instantiation: _RNCNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB8_8Keystore3new0s0_0Bc_
Unexecuted instantiation: _RNCNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB8_8Keystore3new0s0_0CsjyNE3yDMkgA_14json_rpc_basic
Unexecuted instantiation: _RNCNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB8_8Keystore3new0s0_0Cs4VrkfB1pvQ3_25json_rpc_general_requests
178
                    ),
179
                )));
180
181
2
                let (namespace, _, algorithm, _, public_key) =
182
2
                    match nom::Parser::parse(&mut parser, &file_name) {
183
2
                        Ok((_, v)) => v,
184
0
                        Err(_) => continue,
185
                    };
186
187
                // Make sure that the content of the file is valid and that it corresponds to
188
                // the public key advertised in the file name.
189
2
                match algorithm {
190
                    PrivateKey::FileEd25519 => {
191
1
                        match Self::load_ed25519_from_file(keys_directory.join(entry.path())).await
192
                        {
193
1
                            Ok(kp) => {
194
1
                                if ed25519_zebra::VerificationKey::from(&*kp).as_ref() != public_key
195
                                {
196
0
                                    continue;
197
1
                                }
198
                            }
199
0
                            Err(_) => continue,
200
                        }
201
                    }
202
                    PrivateKey::FileSr25519 => {
203
1
                        match Self::load_sr25519_from_file(keys_directory.join(entry.path())).await
204
                        {
205
1
                            Ok(kp) => {
206
1
                                if kp.public.to_bytes() != public_key {
207
0
                                    continue;
208
1
                                }
209
                            }
210
0
                            Err(err) => panic!("{err:?}"),
211
                        }
212
                    }
213
0
                    _ => unreachable!(),
214
                }
215
216
2
                keys.insert((namespace, public_key), algorithm);
217
            }
218
21
        }
219
220
25
        Ok(Keystore {
221
25
            keys_directory,
222
25
            guarded: Mutex::new(Guarded { gen_rng, keys }),
223
25
            sr25519_signing_context: schnorrkel::signing_context(b"substrate"),
224
25
        })
225
25
    }
_RNCNvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB6_8Keystore3new0Ba_
Line
Count
Source
121
4
    ) -> Result<Self, io::Error> {
122
4
        let mut gen_rng = rand_chacha::ChaCha20Rng::from_seed(randomness_seed);
123
124
4
        let mut keys = hashbrown::HashMap::with_capacity_and_hasher(32, {
125
4
            SipHasherBuild::new({
126
4
                let mut seed = [0; 16];
127
4
                gen_rng.fill_bytes(&mut seed);
128
4
                seed
129
            })
130
        });
131
132
        // Load the keys from the disk.
133
        // TODO: return some diagnostic about invalid files?
134
4
        if let Some(keys_directory) = &keys_directory {
135
4
            if !keys_directory.try_exists()
?0
{
136
0
                fs::create_dir_all(keys_directory)?;
137
4
            }
138
139
4
            for 
entry2
in fs::read_dir(keys_directory)
?0
{
140
2
                let entry = entry
?0
;
141
2
                if entry.file_type()
?0
.is_dir() {
142
0
                    continue;
143
2
                }
144
145
                // Try to match the file name.
146
2
                let file_name = match entry.file_name().into_string() {
147
2
                    Ok(n) => n,
148
0
                    Err(_) => continue,
149
                };
150
151
2
                let mut parser = nom::combinator::all_consuming::<
152
2
                    _,
153
2
                    (&str, nom::error::ErrorKind),
154
2
                    _,
155
2
                >(nom::combinator::complete((
156
2
                    nom::combinator::map_opt(
157
2
                        nom::bytes::streaming::take(4u32),
158
                        KeyNamespace::from_string,
159
                    ),
160
2
                    nom::bytes::streaming::tag("-"),
161
2
                    nom::combinator::map_opt(nom::bytes::streaming::take(7u32), |b| match b {
162
                        "ed25519" => Some(PrivateKey::FileEd25519),
163
                        "sr25519" => Some(PrivateKey::FileSr25519),
164
                        _ => None,
165
                    }),
166
2
                    nom::bytes::streaming::tag("-"),
167
2
                    nom::combinator::map_opt(
168
2
                        nom::bytes::complete::take_while(|c: char| {
169
                            c.is_ascii_digit() || ('a'..='f').contains(&c)
170
                        }),
171
                        |k: &str| {
172
                            if k.len() == 64 {
173
                                Some(<[u8; 32]>::try_from(hex::decode(k).unwrap()).unwrap())
174
                            } else {
175
                                None
176
                            }
177
                        },
178
                    ),
179
                )));
180
181
2
                let (namespace, _, algorithm, _, public_key) =
182
2
                    match nom::Parser::parse(&mut parser, &file_name) {
183
2
                        Ok((_, v)) => v,
184
0
                        Err(_) => continue,
185
                    };
186
187
                // Make sure that the content of the file is valid and that it corresponds to
188
                // the public key advertised in the file name.
189
2
                match algorithm {
190
                    PrivateKey::FileEd25519 => {
191
1
                        match Self::load_ed25519_from_file(keys_directory.join(entry.path())).await
192
                        {
193
1
                            Ok(kp) => {
194
1
                                if ed25519_zebra::VerificationKey::from(&*kp).as_ref() != public_key
195
                                {
196
0
                                    continue;
197
1
                                }
198
                            }
199
0
                            Err(_) => continue,
200
                        }
201
                    }
202
                    PrivateKey::FileSr25519 => {
203
1
                        match Self::load_sr25519_from_file(keys_directory.join(entry.path())).await
204
                        {
205
1
                            Ok(kp) => {
206
1
                                if kp.public.to_bytes() != public_key {
207
0
                                    continue;
208
1
                                }
209
                            }
210
0
                            Err(err) => panic!("{err:?}"),
211
                        }
212
                    }
213
0
                    _ => unreachable!(),
214
                }
215
216
2
                keys.insert((namespace, public_key), algorithm);
217
            }
218
0
        }
219
220
4
        Ok(Keystore {
221
4
            keys_directory,
222
4
            guarded: Mutex::new(Guarded { gen_rng, keys }),
223
4
            sr25519_signing_context: schnorrkel::signing_context(b"substrate"),
224
4
        })
225
4
    }
Unexecuted instantiation: _RNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB6_8Keystore3new0CscoAnRPySggw_6author
Unexecuted instantiation: _RNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB6_8Keystore3new0Ba_
_RNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB6_8Keystore3new0CsjyNE3yDMkgA_14json_rpc_basic
Line
Count
Source
121
2
    ) -> Result<Self, io::Error> {
122
2
        let mut gen_rng = rand_chacha::ChaCha20Rng::from_seed(randomness_seed);
123
124
2
        let mut keys = hashbrown::HashMap::with_capacity_and_hasher(32, {
125
2
            SipHasherBuild::new({
126
2
                let mut seed = [0; 16];
127
2
                gen_rng.fill_bytes(&mut seed);
128
2
                seed
129
            })
130
        });
131
132
        // Load the keys from the disk.
133
        // TODO: return some diagnostic about invalid files?
134
2
        if let Some(
keys_directory0
) = &keys_directory {
135
0
            if !keys_directory.try_exists()? {
136
0
                fs::create_dir_all(keys_directory)?;
137
0
            }
138
139
0
            for entry in fs::read_dir(keys_directory)? {
140
0
                let entry = entry?;
141
0
                if entry.file_type()?.is_dir() {
142
0
                    continue;
143
0
                }
144
145
                // Try to match the file name.
146
0
                let file_name = match entry.file_name().into_string() {
147
0
                    Ok(n) => n,
148
0
                    Err(_) => continue,
149
                };
150
151
0
                let mut parser = nom::combinator::all_consuming::<
152
0
                    _,
153
0
                    (&str, nom::error::ErrorKind),
154
0
                    _,
155
0
                >(nom::combinator::complete((
156
0
                    nom::combinator::map_opt(
157
0
                        nom::bytes::streaming::take(4u32),
158
                        KeyNamespace::from_string,
159
                    ),
160
0
                    nom::bytes::streaming::tag("-"),
161
0
                    nom::combinator::map_opt(nom::bytes::streaming::take(7u32), |b| match b {
162
                        "ed25519" => Some(PrivateKey::FileEd25519),
163
                        "sr25519" => Some(PrivateKey::FileSr25519),
164
                        _ => None,
165
                    }),
166
0
                    nom::bytes::streaming::tag("-"),
167
0
                    nom::combinator::map_opt(
168
0
                        nom::bytes::complete::take_while(|c: char| {
169
                            c.is_ascii_digit() || ('a'..='f').contains(&c)
170
                        }),
171
                        |k: &str| {
172
                            if k.len() == 64 {
173
                                Some(<[u8; 32]>::try_from(hex::decode(k).unwrap()).unwrap())
174
                            } else {
175
                                None
176
                            }
177
                        },
178
                    ),
179
                )));
180
181
0
                let (namespace, _, algorithm, _, public_key) =
182
0
                    match nom::Parser::parse(&mut parser, &file_name) {
183
0
                        Ok((_, v)) => v,
184
0
                        Err(_) => continue,
185
                    };
186
187
                // Make sure that the content of the file is valid and that it corresponds to
188
                // the public key advertised in the file name.
189
0
                match algorithm {
190
                    PrivateKey::FileEd25519 => {
191
0
                        match Self::load_ed25519_from_file(keys_directory.join(entry.path())).await
192
                        {
193
0
                            Ok(kp) => {
194
0
                                if ed25519_zebra::VerificationKey::from(&*kp).as_ref() != public_key
195
                                {
196
0
                                    continue;
197
0
                                }
198
                            }
199
0
                            Err(_) => continue,
200
                        }
201
                    }
202
                    PrivateKey::FileSr25519 => {
203
0
                        match Self::load_sr25519_from_file(keys_directory.join(entry.path())).await
204
                        {
205
0
                            Ok(kp) => {
206
0
                                if kp.public.to_bytes() != public_key {
207
0
                                    continue;
208
0
                                }
209
                            }
210
0
                            Err(err) => panic!("{err:?}"),
211
                        }
212
                    }
213
0
                    _ => unreachable!(),
214
                }
215
216
0
                keys.insert((namespace, public_key), algorithm);
217
            }
218
2
        }
219
220
2
        Ok(Keystore {
221
2
            keys_directory,
222
2
            guarded: Mutex::new(Guarded { gen_rng, keys }),
223
2
            sr25519_signing_context: schnorrkel::signing_context(b"substrate"),
224
2
        })
225
2
    }
_RNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB6_8Keystore3new0Cs4VrkfB1pvQ3_25json_rpc_general_requests
Line
Count
Source
121
19
    ) -> Result<Self, io::Error> {
122
19
        let mut gen_rng = rand_chacha::ChaCha20Rng::from_seed(randomness_seed);
123
124
19
        let mut keys = hashbrown::HashMap::with_capacity_and_hasher(32, {
125
19
            SipHasherBuild::new({
126
19
                let mut seed = [0; 16];
127
19
                gen_rng.fill_bytes(&mut seed);
128
19
                seed
129
            })
130
        });
131
132
        // Load the keys from the disk.
133
        // TODO: return some diagnostic about invalid files?
134
19
        if let Some(
keys_directory0
) = &keys_directory {
135
0
            if !keys_directory.try_exists()? {
136
0
                fs::create_dir_all(keys_directory)?;
137
0
            }
138
139
0
            for entry in fs::read_dir(keys_directory)? {
140
0
                let entry = entry?;
141
0
                if entry.file_type()?.is_dir() {
142
0
                    continue;
143
0
                }
144
145
                // Try to match the file name.
146
0
                let file_name = match entry.file_name().into_string() {
147
0
                    Ok(n) => n,
148
0
                    Err(_) => continue,
149
                };
150
151
0
                let mut parser = nom::combinator::all_consuming::<
152
0
                    _,
153
0
                    (&str, nom::error::ErrorKind),
154
0
                    _,
155
0
                >(nom::combinator::complete((
156
0
                    nom::combinator::map_opt(
157
0
                        nom::bytes::streaming::take(4u32),
158
                        KeyNamespace::from_string,
159
                    ),
160
0
                    nom::bytes::streaming::tag("-"),
161
0
                    nom::combinator::map_opt(nom::bytes::streaming::take(7u32), |b| match b {
162
                        "ed25519" => Some(PrivateKey::FileEd25519),
163
                        "sr25519" => Some(PrivateKey::FileSr25519),
164
                        _ => None,
165
                    }),
166
0
                    nom::bytes::streaming::tag("-"),
167
0
                    nom::combinator::map_opt(
168
0
                        nom::bytes::complete::take_while(|c: char| {
169
                            c.is_ascii_digit() || ('a'..='f').contains(&c)
170
                        }),
171
                        |k: &str| {
172
                            if k.len() == 64 {
173
                                Some(<[u8; 32]>::try_from(hex::decode(k).unwrap()).unwrap())
174
                            } else {
175
                                None
176
                            }
177
                        },
178
                    ),
179
                )));
180
181
0
                let (namespace, _, algorithm, _, public_key) =
182
0
                    match nom::Parser::parse(&mut parser, &file_name) {
183
0
                        Ok((_, v)) => v,
184
0
                        Err(_) => continue,
185
                    };
186
187
                // Make sure that the content of the file is valid and that it corresponds to
188
                // the public key advertised in the file name.
189
0
                match algorithm {
190
                    PrivateKey::FileEd25519 => {
191
0
                        match Self::load_ed25519_from_file(keys_directory.join(entry.path())).await
192
                        {
193
0
                            Ok(kp) => {
194
0
                                if ed25519_zebra::VerificationKey::from(&*kp).as_ref() != public_key
195
                                {
196
0
                                    continue;
197
0
                                }
198
                            }
199
0
                            Err(_) => continue,
200
                        }
201
                    }
202
                    PrivateKey::FileSr25519 => {
203
0
                        match Self::load_sr25519_from_file(keys_directory.join(entry.path())).await
204
                        {
205
0
                            Ok(kp) => {
206
0
                                if kp.public.to_bytes() != public_key {
207
0
                                    continue;
208
0
                                }
209
                            }
210
0
                            Err(err) => panic!("{err:?}"),
211
                        }
212
                    }
213
0
                    _ => unreachable!(),
214
                }
215
216
0
                keys.insert((namespace, public_key), algorithm);
217
            }
218
19
        }
219
220
19
        Ok(Keystore {
221
19
            keys_directory,
222
19
            guarded: Mutex::new(Guarded { gen_rng, keys }),
223
19
            sr25519_signing_context: schnorrkel::signing_context(b"substrate"),
224
19
        })
225
19
    }
226
227
    /// Inserts an Sr25519 private key in the keystore.
228
    ///
229
    /// Returns the corresponding public key.
230
    ///
231
    /// This is meant to be called with publicly-known private keys. Use
232
    /// [`Keystore::generate_sr25519`] if the private key is meant to actually be private.
233
    ///
234
    /// The key is not saved on disk.
235
    ///
236
    /// # Panic
237
    ///
238
    /// Panics if the key isn't a valid Sr25519 private key. This function is meant to be used
239
    /// with hard coded values which are known to be correct. Please do not call it with any
240
    /// sort of user input.
241
    ///
242
0
    pub fn insert_sr25519_memory(
243
0
        &mut self,
244
0
        namespaces: impl Iterator<Item = KeyNamespace>,
245
0
        private_key: &[u8; 64],
246
0
    ) -> [u8; 32] {
247
        // TODO: we can't wrap this private_key into a `Zeroizing` because of the call to `to_keypair()` below, needs fixing in schnorrkel
248
0
        let private_key = schnorrkel::SecretKey::from_bytes(&private_key[..]).unwrap();
249
0
        let keypair = zeroize::Zeroizing::new(private_key.to_keypair());
250
0
        let public_key = keypair.public.to_bytes();
251
252
0
        for namespace in namespaces {
253
0
            self.guarded.get_mut().keys.insert(
254
0
                (namespace, public_key),
255
0
                PrivateKey::MemorySr25519(keypair.clone()),
256
0
            );
257
0
        }
258
259
0
        public_key
260
0
    }
Unexecuted instantiation: _RINvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB5_8Keystore21insert_sr25519_memorypEB9_
Unexecuted instantiation: _RINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB5_8Keystore21insert_sr25519_memoryINtNtNtCs1p5UDGgVI4d_4core5array4iter8IntoIterNtB5_12KeyNamespaceKj5_EECscoAnRPySggw_6author
Unexecuted instantiation: _RINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB5_8Keystore21insert_sr25519_memorypEB9_
Unexecuted instantiation: _RINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB5_8Keystore21insert_sr25519_memoryINtNtNtCs1p5UDGgVI4d_4core5array4iter8IntoIterNtB5_12KeyNamespaceKj5_EECsjyNE3yDMkgA_14json_rpc_basic
Unexecuted instantiation: _RINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB5_8Keystore21insert_sr25519_memoryINtNtNtCs1p5UDGgVI4d_4core5array4iter8IntoIterNtB5_12KeyNamespaceKj5_EECs4VrkfB1pvQ3_25json_rpc_general_requests
261
262
    /// Generates a new Ed25519 key and inserts it in the keystore.
263
    ///
264
    /// If `save` is `true`, the generated key is saved in the file system. This function returns
265
    /// an error only if `save` is `true` and the key couldn't be written to the file system.
266
    /// The value of `save` is silently ignored if no path was provided to [`Keystore::new`].
267
    ///
268
    /// Returns the corresponding public key.
269
1
    pub async fn generate_ed25519(
270
1
        &self,
271
1
        namespace: KeyNamespace,
272
1
        save: bool,
273
1
    ) -> Result<[u8; 32], io::Error> {
_RNvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB4_8Keystore16generate_ed25519
Line
Count
Source
269
1
    pub async fn generate_ed25519(
270
1
        &self,
271
1
        namespace: KeyNamespace,
272
1
        save: bool,
273
1
    ) -> Result<[u8; 32], io::Error> {
Unexecuted instantiation: _RNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB4_8Keystore16generate_ed25519
274
1
        let mut guarded = self.guarded.lock().await;
275
276
        // Note: it is in principle possible to generate some entropy from the PRNG, then unlock
277
        // the mutex while the private key is being generated. This reduces the time during which
278
        // the mutex is locked, but in practice generating a key is a rare enough event that this
279
        // is not worth the effort.
280
1
        let private_key =
281
1
            zeroize::Zeroizing::new(ed25519_zebra::SigningKey::new(&mut guarded.gen_rng));
282
1
        let public_key: [u8; 32] = ed25519_zebra::VerificationKey::from(&*private_key).into();
283
284
1
        let save_path = if save {
285
1
            self.path_of_key_ed25519(namespace, &public_key)
286
        } else {
287
0
            None
288
        };
289
290
1
        if let Some(save_path) = save_path {
291
1
            Self::write_to_file_ed25519(&save_path, &private_key).await
?0
;
292
1
            guarded
293
1
                .keys
294
1
                .insert((namespace, public_key), PrivateKey::FileEd25519);
295
0
        } else {
296
0
            guarded.keys.insert(
297
0
                (namespace, public_key),
298
0
                PrivateKey::MemoryEd25519(private_key),
299
0
            );
300
0
        }
301
302
1
        Ok(public_key)
303
1
    }
_RNCNvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB6_8Keystore16generate_ed255190Ba_
Line
Count
Source
273
1
    ) -> Result<[u8; 32], io::Error> {
274
1
        let mut guarded = self.guarded.lock().await;
275
276
        // Note: it is in principle possible to generate some entropy from the PRNG, then unlock
277
        // the mutex while the private key is being generated. This reduces the time during which
278
        // the mutex is locked, but in practice generating a key is a rare enough event that this
279
        // is not worth the effort.
280
1
        let private_key =
281
1
            zeroize::Zeroizing::new(ed25519_zebra::SigningKey::new(&mut guarded.gen_rng));
282
1
        let public_key: [u8; 32] = ed25519_zebra::VerificationKey::from(&*private_key).into();
283
284
1
        let save_path = if save {
285
1
            self.path_of_key_ed25519(namespace, &public_key)
286
        } else {
287
0
            None
288
        };
289
290
1
        if let Some(save_path) = save_path {
291
1
            Self::write_to_file_ed25519(&save_path, &private_key).await
?0
;
292
1
            guarded
293
1
                .keys
294
1
                .insert((namespace, public_key), PrivateKey::FileEd25519);
295
0
        } else {
296
0
            guarded.keys.insert(
297
0
                (namespace, public_key),
298
0
                PrivateKey::MemoryEd25519(private_key),
299
0
            );
300
0
        }
301
302
1
        Ok(public_key)
303
1
    }
Unexecuted instantiation: _RNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB6_8Keystore16generate_ed255190Ba_
304
305
    /// Returns the list of all keys known to this keystore.
306
    ///
307
    /// > **Note**: Keep in mind that this function is racy, as keys can be added and removed
308
    /// >           in parallel of this function being called.
309
2
    pub async fn keys(&self) -> impl Iterator<Item = (KeyNamespace, [u8; 32])> {
_RNvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB4_8Keystore4keys
Line
Count
Source
309
2
    pub async fn keys(&self) -> impl Iterator<Item = (KeyNamespace, [u8; 32])> {
Unexecuted instantiation: _RNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB4_8Keystore4keys
310
2
        let guarded = self.guarded.lock().await;
311
2
        guarded.keys.keys().cloned().collect::<Vec<_>>().into_iter()
312
2
    }
_RNCNvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB6_8Keystore4keys0Ba_
Line
Count
Source
309
2
    pub async fn keys(&self) -> impl Iterator<Item = (KeyNamespace, [u8; 32])> {
310
2
        let guarded = self.guarded.lock().await;
311
2
        guarded.keys.keys().cloned().collect::<Vec<_>>().into_iter()
312
2
    }
Unexecuted instantiation: _RNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB6_8Keystore4keys0Ba_
313
314
    /// Generates a new Sr25519 key and inserts it in the keystore.
315
    ///
316
    /// If `save` is `true`, the generated key is saved in the file system. This function returns
317
    /// an error only if `save` is `true` and the key couldn't be written to the file system.
318
    /// The value of `save` is silently ignored if no path was provided to [`Keystore::new`].
319
    ///
320
    /// Returns the corresponding public key.
321
1
    pub async fn generate_sr25519(
322
1
        &self,
323
1
        namespace: KeyNamespace,
324
1
        save: bool,
325
1
    ) -> Result<[u8; 32], io::Error> {
_RNvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB4_8Keystore16generate_sr25519
Line
Count
Source
321
1
    pub async fn generate_sr25519(
322
1
        &self,
323
1
        namespace: KeyNamespace,
324
1
        save: bool,
325
1
    ) -> Result<[u8; 32], io::Error> {
Unexecuted instantiation: _RNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB4_8Keystore16generate_sr25519
326
1
        let mut guarded = self.guarded.lock().await;
327
328
        // Note: it is in principle possible to generate some entropy from the PRNG, then unlock
329
        // the mutex while the private key is being generated. This reduces the time during which
330
        // the mutex is locked, but in practice generating a key is a rare enough event that this
331
        // is not worth the effort.
332
1
        let mini_secret = zeroize::Zeroizing::new(schnorrkel::MiniSecretKey::generate_with(
333
1
            &mut guarded.gen_rng,
334
        ));
335
1
        let keypair = zeroize::Zeroizing::new(
336
1
            mini_secret.expand_to_keypair(schnorrkel::ExpansionMode::Ed25519),
337
        );
338
1
        let public_key = keypair.public.to_bytes();
339
340
1
        let save_path = if save {
341
1
            self.path_of_key_sr25519(namespace, &public_key)
342
        } else {
343
0
            None
344
        };
345
346
1
        if let Some(save_path) = save_path {
347
1
            Self::write_to_file_sr25519(&save_path, &mini_secret).await
?0
;
348
1
            guarded
349
1
                .keys
350
1
                .insert((namespace, public_key), PrivateKey::FileSr25519);
351
0
        } else {
352
0
            guarded
353
0
                .keys
354
0
                .insert((namespace, public_key), PrivateKey::MemorySr25519(keypair));
355
0
        }
356
357
1
        Ok(public_key)
358
1
    }
_RNCNvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB6_8Keystore16generate_sr255190Ba_
Line
Count
Source
325
1
    ) -> Result<[u8; 32], io::Error> {
326
1
        let mut guarded = self.guarded.lock().await;
327
328
        // Note: it is in principle possible to generate some entropy from the PRNG, then unlock
329
        // the mutex while the private key is being generated. This reduces the time during which
330
        // the mutex is locked, but in practice generating a key is a rare enough event that this
331
        // is not worth the effort.
332
1
        let mini_secret = zeroize::Zeroizing::new(schnorrkel::MiniSecretKey::generate_with(
333
1
            &mut guarded.gen_rng,
334
        ));
335
1
        let keypair = zeroize::Zeroizing::new(
336
1
            mini_secret.expand_to_keypair(schnorrkel::ExpansionMode::Ed25519),
337
        );
338
1
        let public_key = keypair.public.to_bytes();
339
340
1
        let save_path = if save {
341
1
            self.path_of_key_sr25519(namespace, &public_key)
342
        } else {
343
0
            None
344
        };
345
346
1
        if let Some(save_path) = save_path {
347
1
            Self::write_to_file_sr25519(&save_path, &mini_secret).await
?0
;
348
1
            guarded
349
1
                .keys
350
1
                .insert((namespace, public_key), PrivateKey::FileSr25519);
351
0
        } else {
352
0
            guarded
353
0
                .keys
354
0
                .insert((namespace, public_key), PrivateKey::MemorySr25519(keypair));
355
0
        }
356
357
1
        Ok(public_key)
358
1
    }
Unexecuted instantiation: _RNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB6_8Keystore16generate_sr255190Ba_
359
360
    /// Signs the given payload using the private key associated to the public key passed as
361
    /// parameter.
362
    ///
363
    /// An error is returned if the key-namespace combination is not in the keystore, or if the
364
    /// key couldn't be loaded from disk. In the case when a key couldn't be loaded from disk, it
365
    /// is automatically removed from the keystore.
366
2
    pub async fn sign(
367
2
        &self,
368
2
        key_namespace: KeyNamespace,
369
2
        public_key: &[u8; 32],
370
2
        payload: &[u8],
371
2
    ) -> Result<[u8; 64], SignError> {
_RNvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB4_8Keystore4sign
Line
Count
Source
366
2
    pub async fn sign(
367
2
        &self,
368
2
        key_namespace: KeyNamespace,
369
2
        public_key: &[u8; 32],
370
2
        payload: &[u8],
371
2
    ) -> Result<[u8; 64], SignError> {
Unexecuted instantiation: _RNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB4_8Keystore4sign
372
2
        let mut guarded = self.guarded.lock().await;
373
2
        let key = guarded
374
2
            .keys
375
2
            .get(&(key_namespace, *public_key))
376
2
            .ok_or(SignError::UnknownPublicKey)
?0
;
377
378
2
        match key {
379
0
            PrivateKey::MemoryEd25519(key) => Ok(key.sign(payload).into()),
380
            PrivateKey::FileEd25519 => {
381
1
                match Self::load_ed25519_from_file(
382
1
                    self.path_of_key_ed25519(key_namespace, public_key).unwrap(),
383
                )
384
1
                .await
385
                {
386
1
                    Ok(key) => {
387
1
                        drop(guarded);
388
1
                        Ok(key.sign(payload).into())
389
                    }
390
0
                    Err(err) => {
391
0
                        guarded.keys.remove(&(key_namespace, *public_key));
392
0
                        Err(err.into())
393
                    }
394
                }
395
            }
396
0
            PrivateKey::MemorySr25519(key) => Ok(key
397
0
                .sign(self.sr25519_signing_context.bytes(payload))
398
0
                .to_bytes()),
399
            PrivateKey::FileSr25519 => {
400
1
                match Self::load_sr25519_from_file(
401
1
                    self.path_of_key_sr25519(key_namespace, public_key).unwrap(),
402
                )
403
1
                .await
404
                {
405
1
                    Ok(key) => {
406
1
                        drop(guarded);
407
1
                        Ok(key
408
1
                            .sign(self.sr25519_signing_context.bytes(payload))
409
1
                            .to_bytes())
410
                    }
411
0
                    Err(err) => {
412
0
                        guarded.keys.remove(&(key_namespace, *public_key));
413
0
                        Err(err.into())
414
                    }
415
                }
416
            }
417
        }
418
2
    }
_RNCNvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB6_8Keystore4sign0Ba_
Line
Count
Source
371
2
    ) -> Result<[u8; 64], SignError> {
372
2
        let mut guarded = self.guarded.lock().await;
373
2
        let key = guarded
374
2
            .keys
375
2
            .get(&(key_namespace, *public_key))
376
2
            .ok_or(SignError::UnknownPublicKey)
?0
;
377
378
2
        match key {
379
0
            PrivateKey::MemoryEd25519(key) => Ok(key.sign(payload).into()),
380
            PrivateKey::FileEd25519 => {
381
1
                match Self::load_ed25519_from_file(
382
1
                    self.path_of_key_ed25519(key_namespace, public_key).unwrap(),
383
                )
384
1
                .await
385
                {
386
1
                    Ok(key) => {
387
1
                        drop(guarded);
388
1
                        Ok(key.sign(payload).into())
389
                    }
390
0
                    Err(err) => {
391
0
                        guarded.keys.remove(&(key_namespace, *public_key));
392
0
                        Err(err.into())
393
                    }
394
                }
395
            }
396
0
            PrivateKey::MemorySr25519(key) => Ok(key
397
0
                .sign(self.sr25519_signing_context.bytes(payload))
398
0
                .to_bytes()),
399
            PrivateKey::FileSr25519 => {
400
1
                match Self::load_sr25519_from_file(
401
1
                    self.path_of_key_sr25519(key_namespace, public_key).unwrap(),
402
                )
403
1
                .await
404
                {
405
1
                    Ok(key) => {
406
1
                        drop(guarded);
407
1
                        Ok(key
408
1
                            .sign(self.sr25519_signing_context.bytes(payload))
409
1
                            .to_bytes())
410
                    }
411
0
                    Err(err) => {
412
0
                        guarded.keys.remove(&(key_namespace, *public_key));
413
0
                        Err(err.into())
414
                    }
415
                }
416
            }
417
        }
418
2
    }
Unexecuted instantiation: _RNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB6_8Keystore4sign0CscoAnRPySggw_6author
Unexecuted instantiation: _RNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB6_8Keystore4sign0Ba_
Unexecuted instantiation: _RNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB6_8Keystore4sign0CsjyNE3yDMkgA_14json_rpc_basic
Unexecuted instantiation: _RNCNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB6_8Keystore4sign0Cs4VrkfB1pvQ3_25json_rpc_general_requests
419
420
    // TODO: doc
421
    ///
422
    /// Note that the labels must be `'static` due to requirements from the underlying library.
423
    // TODO: unclear why this can't be an async function; getting lifetime errors
424
0
    pub fn sign_sr25519_vrf<'a>(
425
0
        &'a self,
426
0
        key_namespace: KeyNamespace,
427
0
        public_key: &'a [u8; 32],
428
0
        label: &'static [u8],
429
0
        transcript_items: impl Iterator<Item = (&'static [u8], either::Either<&'a [u8], u64>)> + 'a,
430
0
    ) -> impl Future<Output = Result<VrfSignature, SignVrfError>> {
431
0
        async move {
432
0
            let mut guarded = self.guarded.lock().await;
433
0
            let key = guarded
434
0
                .keys
435
0
                .get(&(key_namespace, *public_key))
436
0
                .ok_or(SignVrfError::Sign(SignError::UnknownPublicKey))?;
437
438
0
            match key {
439
                PrivateKey::MemoryEd25519(_) | PrivateKey::FileEd25519 => {
440
0
                    Err(SignVrfError::WrongKeyAlgorithm)
441
                }
442
                PrivateKey::MemorySr25519(_) | PrivateKey::FileSr25519 => {
443
0
                    let key = match key {
444
0
                        PrivateKey::MemorySr25519(key) => Cow::Borrowed(key),
445
                        PrivateKey::FileSr25519 => {
446
0
                            match Self::load_sr25519_from_file(
447
0
                                self.path_of_key_sr25519(key_namespace, public_key).unwrap(),
448
                            )
449
0
                            .await
450
                            {
451
0
                                Ok(key) => {
452
0
                                    drop(guarded);
453
0
                                    Cow::Owned(key)
454
                                }
455
0
                                Err(err) => {
456
0
                                    guarded.keys.remove(&(key_namespace, *public_key));
457
0
                                    return Err(err.into());
458
                                }
459
                            }
460
                        }
461
0
                        _ => unreachable!(),
462
                    };
463
464
0
                    let mut transcript = merlin::Transcript::new(label);
465
0
                    for (label, value) in transcript_items {
466
0
                        match value {
467
0
                            either::Left(bytes) => {
468
0
                                transcript.append_message(label, bytes);
469
0
                            }
470
0
                            either::Right(value) => {
471
0
                                transcript.append_u64(label, value);
472
0
                            }
473
                        }
474
                    }
475
476
0
                    let (_in_out, proof, _) = key.vrf_sign(transcript);
477
0
                    Ok(VrfSignature {
478
0
                        // TODO: should probably output the `_in_out` as well
479
0
                        proof: proof.to_bytes(),
480
0
                    })
481
                }
482
            }
483
0
        }
Unexecuted instantiation: _RNCINvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB7_8Keystore16sign_sr25519_vrfpE0Bb_
Unexecuted instantiation: _RNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB7_8Keystore16sign_sr25519_vrfpE0Bb_
484
0
    }
Unexecuted instantiation: _RINvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB5_8Keystore16sign_sr25519_vrfpEB9_
Unexecuted instantiation: _RINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB5_8Keystore16sign_sr25519_vrfpEB9_
485
486
2
    async fn load_ed25519_from_file(
487
2
        path: impl AsRef<path::Path>,
488
2
    ) -> Result<zeroize::Zeroizing<ed25519_zebra::SigningKey>, KeyLoadError> {
_RINvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB5_8Keystore22load_ed25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufEB9_
Line
Count
Source
486
2
    async fn load_ed25519_from_file(
487
2
        path: impl AsRef<path::Path>,
488
2
    ) -> Result<zeroize::Zeroizing<ed25519_zebra::SigningKey>, KeyLoadError> {
Unexecuted instantiation: _RINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB5_8Keystore22load_ed25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufECscoAnRPySggw_6author
Unexecuted instantiation: _RINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB5_8Keystore22load_ed25519_from_filepEB9_
Unexecuted instantiation: _RINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB5_8Keystore22load_ed25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufECsjyNE3yDMkgA_14json_rpc_basic
Unexecuted instantiation: _RINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB5_8Keystore22load_ed25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufECs4VrkfB1pvQ3_25json_rpc_general_requests
489
        // TODO: read asynchronously?
490
2
        let bytes = fs::read(path).map_err(KeyLoadError::Io)
?0
;
491
2
        let phrase =
492
2
            str::from_utf8(&bytes).map_err(|err| KeyLoadError::BadFormat(
err0
.
to_string0
()))
?0
;
Unexecuted instantiation: _RNCNCINvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB9_8Keystore22load_ed25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE00Bd_
Unexecuted instantiation: _RNCNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB9_8Keystore22load_ed25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE00CscoAnRPySggw_6author
Unexecuted instantiation: _RNCNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB9_8Keystore22load_ed25519_from_filepE00Bd_
Unexecuted instantiation: _RNCNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB9_8Keystore22load_ed25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE00CsjyNE3yDMkgA_14json_rpc_basic
Unexecuted instantiation: _RNCNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB9_8Keystore22load_ed25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE00Cs4VrkfB1pvQ3_25json_rpc_general_requests
493
2
        let mut private_key = seed_phrase::decode_ed25519_private_key(phrase)
494
2
            .map_err(|err| KeyLoadError::BadFormat(
err0
.
to_string0
()))
?0
;
Unexecuted instantiation: _RNCNCINvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB9_8Keystore22load_ed25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE0s_0Bd_
Unexecuted instantiation: _RNCNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB9_8Keystore22load_ed25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE0s_0CscoAnRPySggw_6author
Unexecuted instantiation: _RNCNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB9_8Keystore22load_ed25519_from_filepE0s_0Bd_
Unexecuted instantiation: _RNCNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB9_8Keystore22load_ed25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE0s_0CsjyNE3yDMkgA_14json_rpc_basic
Unexecuted instantiation: _RNCNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB9_8Keystore22load_ed25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE0s_0Cs4VrkfB1pvQ3_25json_rpc_general_requests
495
2
        let zebra_key = zeroize::Zeroizing::new(ed25519_zebra::SigningKey::from(*private_key));
496
2
        zeroize::Zeroize::zeroize(&mut *private_key);
497
2
        Ok(zebra_key)
498
2
    }
_RNCINvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB7_8Keystore22load_ed25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE0Bb_
Line
Count
Source
488
2
    ) -> Result<zeroize::Zeroizing<ed25519_zebra::SigningKey>, KeyLoadError> {
489
        // TODO: read asynchronously?
490
2
        let bytes = fs::read(path).map_err(KeyLoadError::Io)
?0
;
491
2
        let phrase =
492
2
            str::from_utf8(&bytes).map_err(|err| KeyLoadError::BadFormat(err.to_string()))
?0
;
493
2
        let mut private_key = seed_phrase::decode_ed25519_private_key(phrase)
494
2
            .map_err(|err| KeyLoadError::BadFormat(err.to_string()))
?0
;
495
2
        let zebra_key = zeroize::Zeroizing::new(ed25519_zebra::SigningKey::from(*private_key));
496
2
        zeroize::Zeroize::zeroize(&mut *private_key);
497
2
        Ok(zebra_key)
498
2
    }
Unexecuted instantiation: _RNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB7_8Keystore22load_ed25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE0CscoAnRPySggw_6author
Unexecuted instantiation: _RNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB7_8Keystore22load_ed25519_from_filepE0Bb_
Unexecuted instantiation: _RNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB7_8Keystore22load_ed25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE0CsjyNE3yDMkgA_14json_rpc_basic
Unexecuted instantiation: _RNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB7_8Keystore22load_ed25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE0Cs4VrkfB1pvQ3_25json_rpc_general_requests
499
500
2
    async fn load_sr25519_from_file(
501
2
        path: impl AsRef<path::Path>,
502
2
    ) -> Result<zeroize::Zeroizing<schnorrkel::Keypair>, KeyLoadError> {
_RINvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB5_8Keystore22load_sr25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufEB9_
Line
Count
Source
500
2
    async fn load_sr25519_from_file(
501
2
        path: impl AsRef<path::Path>,
502
2
    ) -> Result<zeroize::Zeroizing<schnorrkel::Keypair>, KeyLoadError> {
Unexecuted instantiation: _RINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB5_8Keystore22load_sr25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufECscoAnRPySggw_6author
Unexecuted instantiation: _RINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB5_8Keystore22load_sr25519_from_filepEB9_
Unexecuted instantiation: _RINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB5_8Keystore22load_sr25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufECsjyNE3yDMkgA_14json_rpc_basic
Unexecuted instantiation: _RINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB5_8Keystore22load_sr25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufECs4VrkfB1pvQ3_25json_rpc_general_requests
503
        // TODO: read asynchronously?
504
2
        let bytes = fs::read(path).map_err(KeyLoadError::Io)
?0
;
505
2
        let phrase =
506
2
            str::from_utf8(&bytes).map_err(|err| KeyLoadError::BadFormat(
err0
.
to_string0
()))
?0
;
Unexecuted instantiation: _RNCNCINvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB9_8Keystore22load_sr25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE00Bd_
Unexecuted instantiation: _RNCNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB9_8Keystore22load_sr25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE00CscoAnRPySggw_6author
Unexecuted instantiation: _RNCNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB9_8Keystore22load_sr25519_from_filepE00Bd_
Unexecuted instantiation: _RNCNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB9_8Keystore22load_sr25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE00CsjyNE3yDMkgA_14json_rpc_basic
Unexecuted instantiation: _RNCNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB9_8Keystore22load_sr25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE00Cs4VrkfB1pvQ3_25json_rpc_general_requests
507
2
        let mut private_key = seed_phrase::decode_sr25519_private_key(phrase)
508
2
            .map_err(|err| KeyLoadError::BadFormat(
err0
.
to_string0
()))
?0
;
Unexecuted instantiation: _RNCNCINvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB9_8Keystore22load_sr25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE0s_0Bd_
Unexecuted instantiation: _RNCNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB9_8Keystore22load_sr25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE0s_0CscoAnRPySggw_6author
Unexecuted instantiation: _RNCNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB9_8Keystore22load_sr25519_from_filepE0s_0Bd_
Unexecuted instantiation: _RNCNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB9_8Keystore22load_sr25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE0s_0CsjyNE3yDMkgA_14json_rpc_basic
Unexecuted instantiation: _RNCNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB9_8Keystore22load_sr25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE0s_0Cs4VrkfB1pvQ3_25json_rpc_general_requests
509
        // `from_bytes` only panics if the key is of the wrong length, which we know can't
510
        // happen here.
511
2
        let schnorrkel_key = zeroize::Zeroizing::new(
512
2
            schnorrkel::SecretKey::from_bytes(&*private_key)
513
2
                .unwrap()
514
2
                .into(),
515
        );
516
2
        zeroize::Zeroize::zeroize(&mut *private_key);
517
2
        Ok(schnorrkel_key)
518
2
    }
_RNCINvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB7_8Keystore22load_sr25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE0Bb_
Line
Count
Source
502
2
    ) -> Result<zeroize::Zeroizing<schnorrkel::Keypair>, KeyLoadError> {
503
        // TODO: read asynchronously?
504
2
        let bytes = fs::read(path).map_err(KeyLoadError::Io)
?0
;
505
2
        let phrase =
506
2
            str::from_utf8(&bytes).map_err(|err| KeyLoadError::BadFormat(err.to_string()))
?0
;
507
2
        let mut private_key = seed_phrase::decode_sr25519_private_key(phrase)
508
2
            .map_err(|err| KeyLoadError::BadFormat(err.to_string()))
?0
;
509
        // `from_bytes` only panics if the key is of the wrong length, which we know can't
510
        // happen here.
511
2
        let schnorrkel_key = zeroize::Zeroizing::new(
512
2
            schnorrkel::SecretKey::from_bytes(&*private_key)
513
2
                .unwrap()
514
2
                .into(),
515
        );
516
2
        zeroize::Zeroize::zeroize(&mut *private_key);
517
2
        Ok(schnorrkel_key)
518
2
    }
Unexecuted instantiation: _RNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB7_8Keystore22load_sr25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE0CscoAnRPySggw_6author
Unexecuted instantiation: _RNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB7_8Keystore22load_sr25519_from_filepE0Bb_
Unexecuted instantiation: _RNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB7_8Keystore22load_sr25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE0CsjyNE3yDMkgA_14json_rpc_basic
Unexecuted instantiation: _RNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB7_8Keystore22load_sr25519_from_fileNtNtCs21ZxWzNybR_3std4path7PathBufE0Cs4VrkfB1pvQ3_25json_rpc_general_requests
519
520
1
    async fn write_to_file_ed25519(
521
1
        path: impl AsRef<path::Path>,
522
1
        key: &ed25519_zebra::SigningKey,
523
1
    ) -> Result<(), io::Error> {
_RINvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB5_8Keystore21write_to_file_ed25519RNtNtCs21ZxWzNybR_3std4path7PathBufEB9_
Line
Count
Source
520
1
    async fn write_to_file_ed25519(
521
1
        path: impl AsRef<path::Path>,
522
1
        key: &ed25519_zebra::SigningKey,
523
1
    ) -> Result<(), io::Error> {
Unexecuted instantiation: _RINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB5_8Keystore21write_to_file_ed25519pEB9_
524
1
        let mut phrase = zeroize::Zeroizing::new(vec![0; key.as_ref().len() * 2]);
525
1
        hex::encode_to_slice(key.as_ref(), &mut phrase).unwrap();
526
1
        Self::write_to_file(path, &phrase).await
527
1
    }
_RNCINvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB7_8Keystore21write_to_file_ed25519RNtNtCs21ZxWzNybR_3std4path7PathBufE0Bb_
Line
Count
Source
523
1
    ) -> Result<(), io::Error> {
524
1
        let mut phrase = zeroize::Zeroizing::new(vec![0; key.as_ref().len() * 2]);
525
1
        hex::encode_to_slice(key.as_ref(), &mut phrase).unwrap();
526
1
        Self::write_to_file(path, &phrase).await
527
1
    }
Unexecuted instantiation: _RNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB7_8Keystore21write_to_file_ed25519pE0Bb_
528
529
1
    async fn write_to_file_sr25519(
530
1
        path: impl AsRef<path::Path>,
531
1
        key: &schnorrkel::MiniSecretKey,
532
1
    ) -> Result<(), io::Error> {
_RINvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB5_8Keystore21write_to_file_sr25519RNtNtCs21ZxWzNybR_3std4path7PathBufEB9_
Line
Count
Source
529
1
    async fn write_to_file_sr25519(
530
1
        path: impl AsRef<path::Path>,
531
1
        key: &schnorrkel::MiniSecretKey,
532
1
    ) -> Result<(), io::Error> {
Unexecuted instantiation: _RINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB5_8Keystore21write_to_file_sr25519pEB9_
533
        // TODO: `to_bytes` isn't zeroize-friendly
534
1
        let bytes = key.to_bytes();
535
1
        let mut phrase = zeroize::Zeroizing::new(vec![0; bytes.len() * 2]);
536
1
        hex::encode_to_slice(bytes, &mut phrase).unwrap();
537
1
        Self::write_to_file(path, &phrase).await
538
1
    }
_RNCINvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB7_8Keystore21write_to_file_sr25519RNtNtCs21ZxWzNybR_3std4path7PathBufE0Bb_
Line
Count
Source
532
1
    ) -> Result<(), io::Error> {
533
        // TODO: `to_bytes` isn't zeroize-friendly
534
1
        let bytes = key.to_bytes();
535
1
        let mut phrase = zeroize::Zeroizing::new(vec![0; bytes.len() * 2]);
536
1
        hex::encode_to_slice(bytes, &mut phrase).unwrap();
537
1
        Self::write_to_file(path, &phrase).await
538
1
    }
Unexecuted instantiation: _RNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB7_8Keystore21write_to_file_sr25519pE0Bb_
539
540
2
    async fn write_to_file(
541
2
        path: impl AsRef<path::Path>,
542
2
        key_phrase: &[u8],
543
2
    ) -> Result<(), io::Error> {
_RINvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB5_8Keystore13write_to_fileRNtNtCs21ZxWzNybR_3std4path7PathBufEB9_
Line
Count
Source
540
2
    async fn write_to_file(
541
2
        path: impl AsRef<path::Path>,
542
2
        key_phrase: &[u8],
543
2
    ) -> Result<(), io::Error> {
Unexecuted instantiation: _RINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB5_8Keystore13write_to_filepEB9_
544
2
        let mut file = fs::File::create(path)
?0
;
545
        // TODO: proper security flags on Windows?
546
        #[cfg(target_family = "unix")]
547
2
        file.set_permissions(std::os::unix::fs::PermissionsExt::from_mode(0o400))
?0
;
548
2
        io::Write::write_all(&mut file, b"0x")
?0
;
549
2
        io::Write::write_all(&mut file, key_phrase)
?0
;
550
2
        io::Write::flush(&mut file)
?0
; // This call is generally useless, but doesn't hurt.
551
2
        file.sync_all()
?0
;
552
2
        Ok(())
553
2
    }
_RNCINvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB7_8Keystore13write_to_fileRNtNtCs21ZxWzNybR_3std4path7PathBufE0Bb_
Line
Count
Source
543
2
    ) -> Result<(), io::Error> {
544
2
        let mut file = fs::File::create(path)
?0
;
545
        // TODO: proper security flags on Windows?
546
        #[cfg(target_family = "unix")]
547
2
        file.set_permissions(std::os::unix::fs::PermissionsExt::from_mode(0o400))
?0
;
548
2
        io::Write::write_all(&mut file, b"0x")
?0
;
549
2
        io::Write::write_all(&mut file, key_phrase)
?0
;
550
2
        io::Write::flush(&mut file)
?0
; // This call is generally useless, but doesn't hurt.
551
2
        file.sync_all()
?0
;
552
2
        Ok(())
553
2
    }
Unexecuted instantiation: _RNCINvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB7_8Keystore13write_to_filepE0Bb_
554
555
2
    fn path_of_key_ed25519(
556
2
        &self,
557
2
        key_namespace: KeyNamespace,
558
2
        public_key: &[u8; 32],
559
2
    ) -> Option<path::PathBuf> {
560
2
        self.path_of_key(key_namespace, "ed25519", public_key)
561
2
    }
_RNvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB4_8Keystore19path_of_key_ed25519
Line
Count
Source
555
2
    fn path_of_key_ed25519(
556
2
        &self,
557
2
        key_namespace: KeyNamespace,
558
2
        public_key: &[u8; 32],
559
2
    ) -> Option<path::PathBuf> {
560
2
        self.path_of_key(key_namespace, "ed25519", public_key)
561
2
    }
Unexecuted instantiation: _RNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB4_8Keystore19path_of_key_ed25519
562
563
2
    fn path_of_key_sr25519(
564
2
        &self,
565
2
        key_namespace: KeyNamespace,
566
2
        public_key: &[u8; 32],
567
2
    ) -> Option<path::PathBuf> {
568
2
        self.path_of_key(key_namespace, "sr25519", public_key)
569
2
    }
_RNvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB4_8Keystore19path_of_key_sr25519
Line
Count
Source
563
2
    fn path_of_key_sr25519(
564
2
        &self,
565
2
        key_namespace: KeyNamespace,
566
2
        public_key: &[u8; 32],
567
2
    ) -> Option<path::PathBuf> {
568
2
        self.path_of_key(key_namespace, "sr25519", public_key)
569
2
    }
Unexecuted instantiation: _RNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB4_8Keystore19path_of_key_sr25519
570
571
4
    fn path_of_key(
572
4
        &self,
573
4
        key_namespace: KeyNamespace,
574
4
        key_algorithm: &str,
575
4
        public_key: &[u8; 32],
576
4
    ) -> Option<path::PathBuf> {
577
4
        let keys_directory = match &self.keys_directory {
578
4
            Some(k) => k,
579
0
            None => return None,
580
        };
581
582
        // We don't use the same pathing scheme as Substrate, for two reasons:
583
        // - The fact that Substrate hex-encodes the namespace is completely unnecessary and
584
        // confusing.
585
        // - Substrate doesn't indicate whether the key is ed25519 or sr25519, because the
586
        // algorithm to use is provided when signing or verifying. This is weird and in my opinion
587
        // not a good practice.
588
589
4
        let mut file_name = String::with_capacity(256); // 256 is more than enough.
590
4
        file_name.push_str(key_namespace.as_string());
591
4
        file_name.push('-');
592
4
        file_name.push_str(key_algorithm);
593
4
        file_name.push('-');
594
4
        file_name.push_str(&hex::encode(public_key));
595
596
4
        let mut path =
597
4
            path::PathBuf::with_capacity(keys_directory.as_os_str().len() + file_name.len() + 16);
598
4
        path.push(keys_directory);
599
4
        path.push(file_name);
600
4
        Some(path)
601
4
    }
_RNvMs_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB4_8Keystore11path_of_key
Line
Count
Source
571
4
    fn path_of_key(
572
4
        &self,
573
4
        key_namespace: KeyNamespace,
574
4
        key_algorithm: &str,
575
4
        public_key: &[u8; 32],
576
4
    ) -> Option<path::PathBuf> {
577
4
        let keys_directory = match &self.keys_directory {
578
4
            Some(k) => k,
579
0
            None => return None,
580
        };
581
582
        // We don't use the same pathing scheme as Substrate, for two reasons:
583
        // - The fact that Substrate hex-encodes the namespace is completely unnecessary and
584
        // confusing.
585
        // - Substrate doesn't indicate whether the key is ed25519 or sr25519, because the
586
        // algorithm to use is provided when signing or verifying. This is weird and in my opinion
587
        // not a good practice.
588
589
4
        let mut file_name = String::with_capacity(256); // 256 is more than enough.
590
4
        file_name.push_str(key_namespace.as_string());
591
4
        file_name.push('-');
592
4
        file_name.push_str(key_algorithm);
593
4
        file_name.push('-');
594
4
        file_name.push_str(&hex::encode(public_key));
595
596
4
        let mut path =
597
4
            path::PathBuf::with_capacity(keys_directory.as_os_str().len() + file_name.len() + 16);
598
4
        path.push(keys_directory);
599
4
        path.push(file_name);
600
4
        Some(path)
601
4
    }
Unexecuted instantiation: _RNvMs_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB4_8Keystore11path_of_key
602
}
603
604
struct Guarded {
605
    gen_rng: rand_chacha::ChaCha20Rng,
606
    keys: hashbrown::HashMap<(KeyNamespace, [u8; 32]), PrivateKey, SipHasherBuild>,
607
}
608
609
pub struct VrfSignature {
610
    pub proof: [u8; 64],
611
}
612
613
#[derive(Debug, derive_more::Display, derive_more::Error)]
614
pub enum SignError {
615
    /// The given `(namespace, public key)` combination is unknown to this keystore.
616
    UnknownPublicKey,
617
618
    /// Error while accessing the file containing the secret key.
619
    /// Typically indicates the content of the file has been modified by something else than
620
    /// the keystore.
621
    #[display("Error loading the secret key; {_0}")]
622
    KeyLoad(KeyLoadError),
623
}
624
625
#[derive(Debug, derive_more::Display, derive_more::Error)]
626
pub enum KeyLoadError {
627
    /// Error reported by the operating system.
628
    #[display("{_0}")]
629
    Io(io::Error),
630
    /// Content of the file is invalid. Contains a human-readable error message as a string.
631
    /// Because the format of the content of the file is an implementation detail, no detail is
632
    /// provided.
633
    #[display("{_0}")]
634
    BadFormat(#[error(not(source))] String),
635
}
636
637
#[derive(Debug, derive_more::Display, derive_more::Error)]
638
pub enum SignVrfError {
639
    #[display("{_0}")]
640
    Sign(SignError),
641
    WrongKeyAlgorithm,
642
}
643
644
enum PrivateKey {
645
    MemoryEd25519(zeroize::Zeroizing<ed25519_zebra::SigningKey>),
646
    MemorySr25519(zeroize::Zeroizing<schnorrkel::Keypair>),
647
    FileEd25519,
648
    FileSr25519,
649
}
650
651
impl From<KeyLoadError> for SignError {
652
0
    fn from(err: KeyLoadError) -> SignError {
653
0
        SignError::KeyLoad(err)
654
0
    }
Unexecuted instantiation: _RNvXs0_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB5_9SignErrorINtNtCs1p5UDGgVI4d_4core7convert4FromNtB5_12KeyLoadErrorE4from
Unexecuted instantiation: _RNvXs0_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB5_9SignErrorINtNtCs1p5UDGgVI4d_4core7convert4FromNtB5_12KeyLoadErrorE4from
655
}
656
657
impl From<KeyLoadError> for SignVrfError {
658
0
    fn from(err: KeyLoadError) -> SignVrfError {
659
0
        SignVrfError::Sign(SignError::KeyLoad(err))
660
0
    }
Unexecuted instantiation: _RNvXs1_NtNtCsjlkOsLH0Zfj_7smoldot8identity8keystoreNtB5_12SignVrfErrorINtNtCs1p5UDGgVI4d_4core7convert4FromNtB5_12KeyLoadErrorE4from
Unexecuted instantiation: _RNvXs1_NtNtCsc1ywvx6YAnK_7smoldot8identity8keystoreNtB5_12SignVrfErrorINtNtCs1p5UDGgVI4d_4core7convert4FromNtB5_12KeyLoadErrorE4from
661
}
662
663
#[cfg(test)]
664
mod tests {
665
    use super::{KeyNamespace, Keystore};
666
667
    #[test]
668
1
    fn disk_storage_works_ed25519() {
669
1
        futures_executor::block_on(async move {
670
1
            let path = tempfile::tempdir().unwrap();
671
672
1
            let keystore1 = Keystore::new(Some(path.path().to_owned()), rand::random())
673
1
                .await
674
1
                .unwrap();
675
1
            let public_key = keystore1
676
1
                .generate_ed25519(KeyNamespace::Babe, true)
677
1
                .await
678
1
                .unwrap();
679
1
            drop(keystore1);
680
681
1
            let keystore2 = Keystore::new(Some(path.path().to_owned()), rand::random())
682
1
                .await
683
1
                .unwrap();
684
1
            assert_eq!(
685
1
                keystore2.keys().await.next(),
686
1
                Some((KeyNamespace::Babe, public_key))
687
            );
688
689
1
            let signature = keystore2
690
1
                .sign(KeyNamespace::Babe, &public_key, b"hello world")
691
1
                .await
692
1
                .unwrap();
693
694
1
            assert!(
695
1
                ed25519_zebra::VerificationKey::try_from(public_key)
696
1
                    .unwrap()
697
1
                    .verify(&ed25519_zebra::Signature::from(signature), b"hello world")
698
1
                    .is_ok()
699
            );
700
1
        });
701
1
    }
702
703
    #[test]
704
1
    fn disk_storage_works_sr25519() {
705
1
        futures_executor::block_on(async move {
706
1
            let path = tempfile::tempdir().unwrap();
707
708
1
            let keystore1 = Keystore::new(Some(path.path().to_owned()), rand::random())
709
1
                .await
710
1
                .unwrap();
711
1
            let public_key = keystore1
712
1
                .generate_sr25519(KeyNamespace::Aura, true)
713
1
                .await
714
1
                .unwrap();
715
1
            drop(keystore1);
716
717
1
            let keystore2 = Keystore::new(Some(path.path().to_owned()), rand::random())
718
1
                .await
719
1
                .unwrap();
720
1
            assert_eq!(
721
1
                keystore2.keys().await.next(),
722
1
                Some((KeyNamespace::Aura, public_key))
723
            );
724
725
1
            let signature = keystore2
726
1
                .sign(KeyNamespace::Aura, &public_key, b"hello world")
727
1
                .await
728
1
                .unwrap();
729
730
1
            assert!(
731
1
                schnorrkel::PublicKey::from_bytes(&public_key)
732
1
                    .unwrap()
733
1
                    .verify_simple(
734
1
                        b"substrate",
735
1
                        b"hello world",
736
1
                        &schnorrkel::Signature::from_bytes(&signature).unwrap()
737
1
                    )
738
1
                    .is_ok()
739
            );
740
1
        });
741
1
    }
742
}