/__w/smoldot/smoldot/repo/lib/src/identity/keystore.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 | | //! 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 | | #![cfg_attr(docsrs, doc(cfg(feature = "std")))] |
45 | | |
46 | | use crate::{identity::seed_phrase, util::SipHasherBuild}; |
47 | | |
48 | | use async_lock::Mutex; |
49 | | use rand_chacha::rand_core::{RngCore as _, SeedableRng as _}; |
50 | | use std::{borrow::Cow, fs, io, path, str}; |
51 | | |
52 | | /// Namespace of the key. |
53 | | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] |
54 | | // TODO: document |
55 | | pub enum KeyNamespace { |
56 | | Aura, |
57 | | AuthorityDiscovery, |
58 | | Babe, |
59 | | Grandpa, |
60 | | ImOnline, |
61 | | // 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) |
62 | | } |
63 | | |
64 | | impl KeyNamespace { |
65 | | /// Returns all existing variants of [`KeyNamespace`]. |
66 | 0 | pub fn all() -> impl ExactSizeIterator<Item = KeyNamespace> { |
67 | 0 | [ |
68 | 0 | KeyNamespace::Aura, |
69 | 0 | KeyNamespace::AuthorityDiscovery, |
70 | 0 | KeyNamespace::Babe, |
71 | 0 | KeyNamespace::Grandpa, |
72 | 0 | KeyNamespace::ImOnline, |
73 | 0 | ] |
74 | 0 | .into_iter() |
75 | 0 | } Unexecuted instantiation: _RNvMNtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB2_12KeyNamespace3all Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB2_12KeyNamespace3all |
76 | | |
77 | 2 | fn from_string(str: &str) -> Option<Self> { |
78 | 2 | match str { |
79 | 2 | "aura" => Some(KeyNamespace::Aura)1 , |
80 | 1 | "audi" => Some(KeyNamespace::AuthorityDiscovery)0 , |
81 | 1 | "babe" => Some(KeyNamespace::Babe), |
82 | 0 | "gran" => Some(KeyNamespace::Grandpa), |
83 | 0 | "imon" => Some(KeyNamespace::ImOnline), |
84 | 0 | _ => None, |
85 | | } |
86 | 2 | } _RNvMNtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB2_12KeyNamespace11from_string Line | Count | Source | 77 | 2 | fn from_string(str: &str) -> Option<Self> { | 78 | 2 | match str { | 79 | 2 | "aura" => Some(KeyNamespace::Aura)1 , | 80 | 1 | "audi" => Some(KeyNamespace::AuthorityDiscovery)0 , | 81 | 1 | "babe" => Some(KeyNamespace::Babe), | 82 | 0 | "gran" => Some(KeyNamespace::Grandpa), | 83 | 0 | "imon" => Some(KeyNamespace::ImOnline), | 84 | 0 | _ => None, | 85 | | } | 86 | 2 | } |
Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB2_12KeyNamespace11from_string |
87 | | |
88 | 4 | fn as_string(&self) -> &'static str { |
89 | 4 | match self { |
90 | 2 | KeyNamespace::Aura => "aura", |
91 | 0 | KeyNamespace::AuthorityDiscovery => "audi", |
92 | 2 | KeyNamespace::Babe => "babe", |
93 | 0 | KeyNamespace::Grandpa => "gran", |
94 | 0 | KeyNamespace::ImOnline => "imon", |
95 | | } |
96 | 4 | } _RNvMNtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB2_12KeyNamespace9as_string Line | Count | Source | 88 | 4 | fn as_string(&self) -> &'static str { | 89 | 4 | match self { | 90 | 2 | KeyNamespace::Aura => "aura", | 91 | 0 | KeyNamespace::AuthorityDiscovery => "audi", | 92 | 2 | KeyNamespace::Babe => "babe", | 93 | 0 | KeyNamespace::Grandpa => "gran", | 94 | 0 | KeyNamespace::ImOnline => "imon", | 95 | | } | 96 | 4 | } |
Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB2_12KeyNamespace9as_string |
97 | | } |
98 | | |
99 | | /// Collection of key pairs. |
100 | | /// |
101 | | /// This module doesn't give you access to the content of private keys, only to signing |
102 | | /// capabilities. |
103 | | pub struct Keystore { |
104 | | keys_directory: Option<path::PathBuf>, |
105 | | guarded: Mutex<Guarded>, |
106 | | /// Cached base signing context cloned when signing with `sr25519`. |
107 | | sr25519_signing_context: schnorrkel::context::SigningContext, |
108 | | } |
109 | | |
110 | | impl Keystore { |
111 | | /// Initializes a new keystore. |
112 | | /// |
113 | | /// Must be passed bytes of entropy that are used to avoid hash collision attacks and to |
114 | | /// generate private keys. |
115 | | /// |
116 | | /// An error is returned if the `keys_directory` couldn't be opened because, for example, of |
117 | | /// some missing permission or because it isn't a directory. |
118 | | /// If the `keys_directory` doesn't exist, it will be created using `fs::create_dir_all`. |
119 | 4 | pub async fn new( |
120 | 4 | keys_directory: Option<path::PathBuf>, |
121 | 4 | randomness_seed: [u8; 32], |
122 | 25 | ) -> Result<Self, io::Error> { _RNvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB4_8Keystore3new Line | Count | Source | 119 | 4 | pub async fn new( | 120 | 4 | keys_directory: Option<path::PathBuf>, | 121 | 4 | randomness_seed: [u8; 32], | 122 | 4 | ) -> Result<Self, io::Error> { |
Unexecuted instantiation: _RNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB4_8Keystore3new |
123 | 25 | let mut gen_rng = rand_chacha::ChaCha20Rng::from_seed(randomness_seed); |
124 | 25 | |
125 | 25 | let mut keys = hashbrown::HashMap::with_capacity_and_hasher(32, { |
126 | 25 | SipHasherBuild::new({ |
127 | 25 | let mut seed = [0; 16]; |
128 | 25 | gen_rng.fill_bytes(&mut seed); |
129 | 25 | seed |
130 | 25 | }) |
131 | 25 | }); |
132 | | |
133 | | // Load the keys from the disk. |
134 | | // TODO: return some diagnostic about invalid files? |
135 | 25 | if let Some(keys_directory4 ) = &keys_directory { |
136 | 4 | if !keys_directory.try_exists()?0 { |
137 | 0 | fs::create_dir_all(keys_directory)?; |
138 | 4 | } |
139 | | |
140 | 4 | for entry2 in fs::read_dir(keys_directory)?0 { |
141 | 2 | let entry = entry?0 ; |
142 | 2 | if entry.file_type()?0 .is_dir() { |
143 | 0 | continue; |
144 | 2 | } |
145 | | |
146 | | // Try to match the file name. |
147 | 2 | let file_name = match entry.file_name().into_string() { |
148 | 2 | Ok(n) => n, |
149 | 0 | Err(_) => continue, |
150 | | }; |
151 | | |
152 | 2 | let mut parser = |
153 | 2 | nom::combinator::all_consuming::<_, _, (&str, nom::error::ErrorKind), _>( |
154 | 2 | nom::combinator::complete(nom::sequence::tuple(( |
155 | 2 | nom::combinator::map_opt( |
156 | 2 | nom::bytes::streaming::take(4u32), |
157 | 2 | KeyNamespace::from_string, |
158 | 2 | ), |
159 | 2 | nom::bytes::streaming::tag("-"), |
160 | 2 | nom::combinator::map_opt( |
161 | 2 | nom::bytes::streaming::take(7u32), |
162 | 2 | |b| match b { |
163 | 2 | "ed25519" => Some(PrivateKey::FileEd25519)1 , |
164 | 1 | "sr25519" => Some(PrivateKey::FileSr25519), |
165 | 0 | _ => None, |
166 | 2 | }, _RNCNCNvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB8_8Keystore3new00Bc_ Line | Count | Source | 162 | 2 | |b| match b { | 163 | 2 | "ed25519" => Some(PrivateKey::FileEd25519)1 , | 164 | 1 | "sr25519" => Some(PrivateKey::FileSr25519), | 165 | 0 | _ => None, | 166 | 2 | }, |
Unexecuted instantiation: _RNCNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB8_8Keystore3new00Bc_ Unexecuted instantiation: _RNCNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB8_8Keystore3new00CsiLzmwikkc22_14json_rpc_basic Unexecuted instantiation: _RNCNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB8_8Keystore3new00CscDgN54JpMGG_6author Unexecuted instantiation: _RNCNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB8_8Keystore3new00CsibGXYHQB8Ea_25json_rpc_general_requests |
167 | 2 | ), |
168 | 2 | nom::bytes::streaming::tag("-"), |
169 | 2 | nom::combinator::map_opt( |
170 | 128 | nom::bytes::complete::take_while(|c: char| { |
171 | 128 | c.is_ascii_digit() || ('a'..='f').contains(&c)54 |
172 | 128 | }), _RNCNCNvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB8_8Keystore3new0s_0Bc_ Line | Count | Source | 170 | 128 | nom::bytes::complete::take_while(|c: char| { | 171 | 128 | c.is_ascii_digit() || ('a'..='f').contains(&c)54 | 172 | 128 | }), |
Unexecuted instantiation: _RNCNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB8_8Keystore3new0s_0Bc_ Unexecuted instantiation: _RNCNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB8_8Keystore3new0s_0CsiLzmwikkc22_14json_rpc_basic Unexecuted instantiation: _RNCNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB8_8Keystore3new0s_0CscDgN54JpMGG_6author Unexecuted instantiation: _RNCNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB8_8Keystore3new0s_0CsibGXYHQB8Ea_25json_rpc_general_requests |
173 | 2 | |k: &str| { |
174 | 2 | if k.len() == 64 { |
175 | 2 | Some(<[u8; 32]>::try_from(hex::decode(k).unwrap()).unwrap()) |
176 | | } else { |
177 | 0 | None |
178 | | } |
179 | 2 | }, _RNCNCNvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB8_8Keystore3new0s0_0Bc_ Line | Count | Source | 173 | 2 | |k: &str| { | 174 | 2 | if k.len() == 64 { | 175 | 2 | Some(<[u8; 32]>::try_from(hex::decode(k).unwrap()).unwrap()) | 176 | | } else { | 177 | 0 | None | 178 | | } | 179 | 2 | }, |
Unexecuted instantiation: _RNCNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB8_8Keystore3new0s0_0Bc_ Unexecuted instantiation: _RNCNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB8_8Keystore3new0s0_0CsiLzmwikkc22_14json_rpc_basic Unexecuted instantiation: _RNCNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB8_8Keystore3new0s0_0CscDgN54JpMGG_6author Unexecuted instantiation: _RNCNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB8_8Keystore3new0s0_0CsibGXYHQB8Ea_25json_rpc_general_requests |
180 | 2 | ), |
181 | 2 | ))), |
182 | 2 | ); |
183 | | |
184 | 2 | let (namespace, _, algorithm, _, public_key) = match parser(&file_name) { |
185 | 2 | Ok((_, v)) => v, |
186 | 0 | Err(_) => continue, |
187 | | }; |
188 | | |
189 | | // Make sure that the content of the file is valid and that it corresponds to |
190 | | // the public key advertised in the file name. |
191 | 2 | match algorithm { |
192 | | PrivateKey::FileEd25519 => { |
193 | 1 | match Self::load_ed25519_from_file(keys_directory.join(entry.path())).await0 |
194 | | { |
195 | 1 | Ok(kp) => { |
196 | 1 | if ed25519_zebra::VerificationKey::from(&*kp).as_ref() != public_key |
197 | | { |
198 | 0 | continue; |
199 | 1 | } |
200 | | } |
201 | 0 | Err(_) => continue, |
202 | | } |
203 | | } |
204 | | PrivateKey::FileSr25519 => { |
205 | 1 | match Self::load_sr25519_from_file(keys_directory.join(entry.path())).await0 |
206 | | { |
207 | 1 | Ok(kp) => { |
208 | 1 | if kp.public.to_bytes() != public_key { |
209 | 0 | continue; |
210 | 1 | } |
211 | | } |
212 | 0 | Err(err) => panic!("{err:?}"), |
213 | | } |
214 | | } |
215 | 0 | _ => unreachable!(), |
216 | | } |
217 | | |
218 | 2 | keys.insert((namespace, public_key), algorithm); |
219 | | } |
220 | 21 | } |
221 | | |
222 | 25 | Ok(Keystore { |
223 | 25 | keys_directory, |
224 | 25 | guarded: Mutex::new(Guarded { gen_rng, keys }), |
225 | 25 | sr25519_signing_context: schnorrkel::signing_context(b"substrate"), |
226 | 25 | }) |
227 | 25 | } _RNCNvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB6_8Keystore3new0Ba_ Line | Count | Source | 122 | 4 | ) -> Result<Self, io::Error> { | 123 | 4 | let mut gen_rng = rand_chacha::ChaCha20Rng::from_seed(randomness_seed); | 124 | 4 | | 125 | 4 | let mut keys = hashbrown::HashMap::with_capacity_and_hasher(32, { | 126 | 4 | SipHasherBuild::new({ | 127 | 4 | let mut seed = [0; 16]; | 128 | 4 | gen_rng.fill_bytes(&mut seed); | 129 | 4 | seed | 130 | 4 | }) | 131 | 4 | }); | 132 | | | 133 | | // Load the keys from the disk. | 134 | | // TODO: return some diagnostic about invalid files? | 135 | 4 | if let Some(keys_directory) = &keys_directory { | 136 | 4 | if !keys_directory.try_exists()?0 { | 137 | 0 | fs::create_dir_all(keys_directory)?; | 138 | 4 | } | 139 | | | 140 | 4 | for entry2 in fs::read_dir(keys_directory)?0 { | 141 | 2 | let entry = entry?0 ; | 142 | 2 | if entry.file_type()?0 .is_dir() { | 143 | 0 | continue; | 144 | 2 | } | 145 | | | 146 | | // Try to match the file name. | 147 | 2 | let file_name = match entry.file_name().into_string() { | 148 | 2 | Ok(n) => n, | 149 | 0 | Err(_) => continue, | 150 | | }; | 151 | | | 152 | 2 | let mut parser = | 153 | 2 | nom::combinator::all_consuming::<_, _, (&str, nom::error::ErrorKind), _>( | 154 | 2 | nom::combinator::complete(nom::sequence::tuple(( | 155 | 2 | nom::combinator::map_opt( | 156 | 2 | nom::bytes::streaming::take(4u32), | 157 | 2 | KeyNamespace::from_string, | 158 | 2 | ), | 159 | 2 | nom::bytes::streaming::tag("-"), | 160 | 2 | nom::combinator::map_opt( | 161 | 2 | nom::bytes::streaming::take(7u32), | 162 | 2 | |b| match b { | 163 | | "ed25519" => Some(PrivateKey::FileEd25519), | 164 | | "sr25519" => Some(PrivateKey::FileSr25519), | 165 | | _ => None, | 166 | 2 | }, | 167 | 2 | ), | 168 | 2 | nom::bytes::streaming::tag("-"), | 169 | 2 | nom::combinator::map_opt( | 170 | 2 | nom::bytes::complete::take_while(|c: char| { | 171 | | c.is_ascii_digit() || ('a'..='f').contains(&c) | 172 | 2 | }), | 173 | 2 | |k: &str| { | 174 | | if k.len() == 64 { | 175 | | Some(<[u8; 32]>::try_from(hex::decode(k).unwrap()).unwrap()) | 176 | | } else { | 177 | | None | 178 | | } | 179 | 2 | }, | 180 | 2 | ), | 181 | 2 | ))), | 182 | 2 | ); | 183 | | | 184 | 2 | let (namespace, _, algorithm, _, public_key) = match parser(&file_name) { | 185 | 2 | Ok((_, v)) => v, | 186 | 0 | Err(_) => continue, | 187 | | }; | 188 | | | 189 | | // Make sure that the content of the file is valid and that it corresponds to | 190 | | // the public key advertised in the file name. | 191 | 2 | match algorithm { | 192 | | PrivateKey::FileEd25519 => { | 193 | 1 | match Self::load_ed25519_from_file(keys_directory.join(entry.path())).await0 | 194 | | { | 195 | 1 | Ok(kp) => { | 196 | 1 | if ed25519_zebra::VerificationKey::from(&*kp).as_ref() != public_key | 197 | | { | 198 | 0 | continue; | 199 | 1 | } | 200 | | } | 201 | 0 | Err(_) => continue, | 202 | | } | 203 | | } | 204 | | PrivateKey::FileSr25519 => { | 205 | 1 | match Self::load_sr25519_from_file(keys_directory.join(entry.path())).await0 | 206 | | { | 207 | 1 | Ok(kp) => { | 208 | 1 | if kp.public.to_bytes() != public_key { | 209 | 0 | continue; | 210 | 1 | } | 211 | | } | 212 | 0 | Err(err) => panic!("{err:?}"), | 213 | | } | 214 | | } | 215 | 0 | _ => unreachable!(), | 216 | | } | 217 | | | 218 | 2 | keys.insert((namespace, public_key), algorithm); | 219 | | } | 220 | 0 | } | 221 | | | 222 | 4 | Ok(Keystore { | 223 | 4 | keys_directory, | 224 | 4 | guarded: Mutex::new(Guarded { gen_rng, keys }), | 225 | 4 | sr25519_signing_context: schnorrkel::signing_context(b"substrate"), | 226 | 4 | }) | 227 | 4 | } |
Unexecuted instantiation: _RNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB6_8Keystore3new0Ba_ _RNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB6_8Keystore3new0CsiLzmwikkc22_14json_rpc_basic Line | Count | Source | 122 | 2 | ) -> Result<Self, io::Error> { | 123 | 2 | let mut gen_rng = rand_chacha::ChaCha20Rng::from_seed(randomness_seed); | 124 | 2 | | 125 | 2 | let mut keys = hashbrown::HashMap::with_capacity_and_hasher(32, { | 126 | 2 | SipHasherBuild::new({ | 127 | 2 | let mut seed = [0; 16]; | 128 | 2 | gen_rng.fill_bytes(&mut seed); | 129 | 2 | seed | 130 | 2 | }) | 131 | 2 | }); | 132 | | | 133 | | // Load the keys from the disk. | 134 | | // TODO: return some diagnostic about invalid files? | 135 | 2 | if let Some(keys_directory0 ) = &keys_directory { | 136 | 0 | if !keys_directory.try_exists()? { | 137 | 0 | fs::create_dir_all(keys_directory)?; | 138 | 0 | } | 139 | | | 140 | 0 | for entry in fs::read_dir(keys_directory)? { | 141 | 0 | let entry = entry?; | 142 | 0 | if entry.file_type()?.is_dir() { | 143 | 0 | continue; | 144 | 0 | } | 145 | | | 146 | | // Try to match the file name. | 147 | 0 | let file_name = match entry.file_name().into_string() { | 148 | 0 | Ok(n) => n, | 149 | 0 | Err(_) => continue, | 150 | | }; | 151 | | | 152 | 0 | let mut parser = | 153 | 0 | nom::combinator::all_consuming::<_, _, (&str, nom::error::ErrorKind), _>( | 154 | 0 | nom::combinator::complete(nom::sequence::tuple(( | 155 | 0 | nom::combinator::map_opt( | 156 | 0 | nom::bytes::streaming::take(4u32), | 157 | 0 | KeyNamespace::from_string, | 158 | 0 | ), | 159 | 0 | nom::bytes::streaming::tag("-"), | 160 | 0 | nom::combinator::map_opt( | 161 | 0 | nom::bytes::streaming::take(7u32), | 162 | 0 | |b| match b { | 163 | | "ed25519" => Some(PrivateKey::FileEd25519), | 164 | | "sr25519" => Some(PrivateKey::FileSr25519), | 165 | | _ => None, | 166 | 0 | }, | 167 | 0 | ), | 168 | 0 | nom::bytes::streaming::tag("-"), | 169 | 0 | nom::combinator::map_opt( | 170 | 0 | nom::bytes::complete::take_while(|c: char| { | 171 | | c.is_ascii_digit() || ('a'..='f').contains(&c) | 172 | 0 | }), | 173 | 0 | |k: &str| { | 174 | | if k.len() == 64 { | 175 | | Some(<[u8; 32]>::try_from(hex::decode(k).unwrap()).unwrap()) | 176 | | } else { | 177 | | None | 178 | | } | 179 | 0 | }, | 180 | 0 | ), | 181 | 0 | ))), | 182 | 0 | ); | 183 | | | 184 | 0 | let (namespace, _, algorithm, _, public_key) = match parser(&file_name) { | 185 | 0 | Ok((_, v)) => v, | 186 | 0 | Err(_) => continue, | 187 | | }; | 188 | | | 189 | | // Make sure that the content of the file is valid and that it corresponds to | 190 | | // the public key advertised in the file name. | 191 | 0 | match algorithm { | 192 | | PrivateKey::FileEd25519 => { | 193 | 0 | match Self::load_ed25519_from_file(keys_directory.join(entry.path())).await | 194 | | { | 195 | 0 | Ok(kp) => { | 196 | 0 | if ed25519_zebra::VerificationKey::from(&*kp).as_ref() != public_key | 197 | | { | 198 | 0 | continue; | 199 | 0 | } | 200 | | } | 201 | 0 | Err(_) => continue, | 202 | | } | 203 | | } | 204 | | PrivateKey::FileSr25519 => { | 205 | 0 | match Self::load_sr25519_from_file(keys_directory.join(entry.path())).await | 206 | | { | 207 | 0 | Ok(kp) => { | 208 | 0 | if kp.public.to_bytes() != public_key { | 209 | 0 | continue; | 210 | 0 | } | 211 | | } | 212 | 0 | Err(err) => panic!("{err:?}"), | 213 | | } | 214 | | } | 215 | 0 | _ => unreachable!(), | 216 | | } | 217 | | | 218 | 0 | keys.insert((namespace, public_key), algorithm); | 219 | | } | 220 | 2 | } | 221 | | | 222 | 2 | Ok(Keystore { | 223 | 2 | keys_directory, | 224 | 2 | guarded: Mutex::new(Guarded { gen_rng, keys }), | 225 | 2 | sr25519_signing_context: schnorrkel::signing_context(b"substrate"), | 226 | 2 | }) | 227 | 2 | } |
Unexecuted instantiation: _RNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB6_8Keystore3new0CscDgN54JpMGG_6author _RNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB6_8Keystore3new0CsibGXYHQB8Ea_25json_rpc_general_requests Line | Count | Source | 122 | 19 | ) -> Result<Self, io::Error> { | 123 | 19 | let mut gen_rng = rand_chacha::ChaCha20Rng::from_seed(randomness_seed); | 124 | 19 | | 125 | 19 | let mut keys = hashbrown::HashMap::with_capacity_and_hasher(32, { | 126 | 19 | SipHasherBuild::new({ | 127 | 19 | let mut seed = [0; 16]; | 128 | 19 | gen_rng.fill_bytes(&mut seed); | 129 | 19 | seed | 130 | 19 | }) | 131 | 19 | }); | 132 | | | 133 | | // Load the keys from the disk. | 134 | | // TODO: return some diagnostic about invalid files? | 135 | 19 | if let Some(keys_directory0 ) = &keys_directory { | 136 | 0 | if !keys_directory.try_exists()? { | 137 | 0 | fs::create_dir_all(keys_directory)?; | 138 | 0 | } | 139 | | | 140 | 0 | for entry in fs::read_dir(keys_directory)? { | 141 | 0 | let entry = entry?; | 142 | 0 | if entry.file_type()?.is_dir() { | 143 | 0 | continue; | 144 | 0 | } | 145 | | | 146 | | // Try to match the file name. | 147 | 0 | let file_name = match entry.file_name().into_string() { | 148 | 0 | Ok(n) => n, | 149 | 0 | Err(_) => continue, | 150 | | }; | 151 | | | 152 | 0 | let mut parser = | 153 | 0 | nom::combinator::all_consuming::<_, _, (&str, nom::error::ErrorKind), _>( | 154 | 0 | nom::combinator::complete(nom::sequence::tuple(( | 155 | 0 | nom::combinator::map_opt( | 156 | 0 | nom::bytes::streaming::take(4u32), | 157 | 0 | KeyNamespace::from_string, | 158 | 0 | ), | 159 | 0 | nom::bytes::streaming::tag("-"), | 160 | 0 | nom::combinator::map_opt( | 161 | 0 | nom::bytes::streaming::take(7u32), | 162 | 0 | |b| match b { | 163 | | "ed25519" => Some(PrivateKey::FileEd25519), | 164 | | "sr25519" => Some(PrivateKey::FileSr25519), | 165 | | _ => None, | 166 | 0 | }, | 167 | 0 | ), | 168 | 0 | nom::bytes::streaming::tag("-"), | 169 | 0 | nom::combinator::map_opt( | 170 | 0 | nom::bytes::complete::take_while(|c: char| { | 171 | | c.is_ascii_digit() || ('a'..='f').contains(&c) | 172 | 0 | }), | 173 | 0 | |k: &str| { | 174 | | if k.len() == 64 { | 175 | | Some(<[u8; 32]>::try_from(hex::decode(k).unwrap()).unwrap()) | 176 | | } else { | 177 | | None | 178 | | } | 179 | 0 | }, | 180 | 0 | ), | 181 | 0 | ))), | 182 | 0 | ); | 183 | | | 184 | 0 | let (namespace, _, algorithm, _, public_key) = match parser(&file_name) { | 185 | 0 | Ok((_, v)) => v, | 186 | 0 | Err(_) => continue, | 187 | | }; | 188 | | | 189 | | // Make sure that the content of the file is valid and that it corresponds to | 190 | | // the public key advertised in the file name. | 191 | 0 | match algorithm { | 192 | | PrivateKey::FileEd25519 => { | 193 | 0 | match Self::load_ed25519_from_file(keys_directory.join(entry.path())).await | 194 | | { | 195 | 0 | Ok(kp) => { | 196 | 0 | if ed25519_zebra::VerificationKey::from(&*kp).as_ref() != public_key | 197 | | { | 198 | 0 | continue; | 199 | 0 | } | 200 | | } | 201 | 0 | Err(_) => continue, | 202 | | } | 203 | | } | 204 | | PrivateKey::FileSr25519 => { | 205 | 0 | match Self::load_sr25519_from_file(keys_directory.join(entry.path())).await | 206 | | { | 207 | 0 | Ok(kp) => { | 208 | 0 | if kp.public.to_bytes() != public_key { | 209 | 0 | continue; | 210 | 0 | } | 211 | | } | 212 | 0 | Err(err) => panic!("{err:?}"), | 213 | | } | 214 | | } | 215 | 0 | _ => unreachable!(), | 216 | | } | 217 | | | 218 | 0 | keys.insert((namespace, public_key), algorithm); | 219 | | } | 220 | 19 | } | 221 | | | 222 | 19 | Ok(Keystore { | 223 | 19 | keys_directory, | 224 | 19 | guarded: Mutex::new(Guarded { gen_rng, keys }), | 225 | 19 | sr25519_signing_context: schnorrkel::signing_context(b"substrate"), | 226 | 19 | }) | 227 | 19 | } |
|
228 | | |
229 | | /// Inserts an Sr25519 private key in the keystore. |
230 | | /// |
231 | | /// Returns the corresponding public key. |
232 | | /// |
233 | | /// This is meant to be called with publicly-known private keys. Use |
234 | | /// [`Keystore::generate_sr25519`] if the private key is meant to actually be private. |
235 | | /// |
236 | | /// The key is not saved on disk. |
237 | | /// |
238 | | /// # Panic |
239 | | /// |
240 | | /// Panics if the key isn't a valid Sr25519 private key. This function is meant to be used |
241 | | /// with hard coded values which are known to be correct. Please do not call it with any |
242 | | /// sort of user input. |
243 | | /// |
244 | 0 | pub fn insert_sr25519_memory( |
245 | 0 | &mut self, |
246 | 0 | namespaces: impl Iterator<Item = KeyNamespace>, |
247 | 0 | private_key: &[u8; 64], |
248 | 0 | ) -> [u8; 32] { |
249 | 0 | // TODO: we can't wrap this private_key into a `Zeroizing` because of the call to `to_keypair()` below, needs fixing in schnorrkel |
250 | 0 | let private_key = schnorrkel::SecretKey::from_bytes(&private_key[..]).unwrap(); |
251 | 0 | let keypair = zeroize::Zeroizing::new(private_key.to_keypair()); |
252 | 0 | let public_key = keypair.public.to_bytes(); |
253 | | |
254 | 0 | for namespace in namespaces { |
255 | 0 | self.guarded.get_mut().keys.insert( |
256 | 0 | (namespace, public_key), |
257 | 0 | PrivateKey::MemorySr25519(keypair.clone()), |
258 | 0 | ); |
259 | 0 | } |
260 | | |
261 | 0 | public_key |
262 | 0 | } Unexecuted instantiation: _RINvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB5_8Keystore21insert_sr25519_memorypEB9_ Unexecuted instantiation: _RINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_8Keystore21insert_sr25519_memorypEB9_ Unexecuted instantiation: _RINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_8Keystore21insert_sr25519_memoryINtNtNtCsaYZPK01V26L_4core5array4iter8IntoIterNtB5_12KeyNamespaceKj5_EECsiLzmwikkc22_14json_rpc_basic Unexecuted instantiation: _RINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_8Keystore21insert_sr25519_memoryINtNtNtCsaYZPK01V26L_4core5array4iter8IntoIterNtB5_12KeyNamespaceKj5_EECscDgN54JpMGG_6author Unexecuted instantiation: _RINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_8Keystore21insert_sr25519_memoryINtNtNtCsaYZPK01V26L_4core5array4iter8IntoIterNtB5_12KeyNamespaceKj5_EECsibGXYHQB8Ea_25json_rpc_general_requests |
263 | | |
264 | | /// Generates a new Ed25519 key and inserts it in the keystore. |
265 | | /// |
266 | | /// If `save` is `true`, the generated key is saved in the file system. This function returns |
267 | | /// an error only if `save` is `true` and the key couldn't be written to the file system. |
268 | | /// The value of `save` is silently ignored if no path was provided to [`Keystore::new`]. |
269 | | /// |
270 | | /// Returns the corresponding public key. |
271 | 1 | pub async fn generate_ed25519( |
272 | 1 | &self, |
273 | 1 | namespace: KeyNamespace, |
274 | 1 | save: bool, |
275 | 1 | ) -> Result<[u8; 32], io::Error> { _RNvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB4_8Keystore16generate_ed25519 Line | Count | Source | 271 | 1 | pub async fn generate_ed25519( | 272 | 1 | &self, | 273 | 1 | namespace: KeyNamespace, | 274 | 1 | save: bool, | 275 | 1 | ) -> Result<[u8; 32], io::Error> { |
Unexecuted instantiation: _RNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB4_8Keystore16generate_ed25519 |
276 | 1 | let mut guarded = self.guarded.lock().await0 ; |
277 | | |
278 | | // Note: it is in principle possible to generate some entropy from the PRNG, then unlock |
279 | | // the mutex while the private key is being generated. This reduces the time during which |
280 | | // the mutex is locked, but in practice generating a key is a rare enough event that this |
281 | | // is not worth the effort. |
282 | 1 | let private_key = |
283 | 1 | zeroize::Zeroizing::new(ed25519_zebra::SigningKey::new(&mut guarded.gen_rng)); |
284 | 1 | let public_key: [u8; 32] = ed25519_zebra::VerificationKey::from(&*private_key).into(); |
285 | | |
286 | 1 | let save_path = if save { |
287 | 1 | self.path_of_key_ed25519(namespace, &public_key) |
288 | | } else { |
289 | 0 | None |
290 | | }; |
291 | | |
292 | 1 | if let Some(save_path) = save_path { |
293 | 1 | Self::write_to_file_ed25519(&save_path, &private_key).await0 ?0 ; |
294 | 1 | guarded |
295 | 1 | .keys |
296 | 1 | .insert((namespace, public_key), PrivateKey::FileEd25519); |
297 | 0 | } else { |
298 | 0 | guarded.keys.insert( |
299 | 0 | (namespace, public_key), |
300 | 0 | PrivateKey::MemoryEd25519(private_key), |
301 | 0 | ); |
302 | 0 | } |
303 | | |
304 | 1 | Ok(public_key) |
305 | 1 | } _RNCNvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB6_8Keystore16generate_ed255190Ba_ Line | Count | Source | 275 | 1 | ) -> Result<[u8; 32], io::Error> { | 276 | 1 | let mut guarded = self.guarded.lock().await0 ; | 277 | | | 278 | | // Note: it is in principle possible to generate some entropy from the PRNG, then unlock | 279 | | // the mutex while the private key is being generated. This reduces the time during which | 280 | | // the mutex is locked, but in practice generating a key is a rare enough event that this | 281 | | // is not worth the effort. | 282 | 1 | let private_key = | 283 | 1 | zeroize::Zeroizing::new(ed25519_zebra::SigningKey::new(&mut guarded.gen_rng)); | 284 | 1 | let public_key: [u8; 32] = ed25519_zebra::VerificationKey::from(&*private_key).into(); | 285 | | | 286 | 1 | let save_path = if save { | 287 | 1 | self.path_of_key_ed25519(namespace, &public_key) | 288 | | } else { | 289 | 0 | None | 290 | | }; | 291 | | | 292 | 1 | if let Some(save_path) = save_path { | 293 | 1 | Self::write_to_file_ed25519(&save_path, &private_key).await0 ?0 ; | 294 | 1 | guarded | 295 | 1 | .keys | 296 | 1 | .insert((namespace, public_key), PrivateKey::FileEd25519); | 297 | 0 | } else { | 298 | 0 | guarded.keys.insert( | 299 | 0 | (namespace, public_key), | 300 | 0 | PrivateKey::MemoryEd25519(private_key), | 301 | 0 | ); | 302 | 0 | } | 303 | | | 304 | 1 | Ok(public_key) | 305 | 1 | } |
Unexecuted instantiation: _RNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB6_8Keystore16generate_ed255190Ba_ |
306 | | |
307 | | /// Returns the list of all keys known to this keystore. |
308 | | /// |
309 | | /// > **Note**: Keep in mind that this function is racy, as keys can be added and removed |
310 | | /// > in parallel of this function being called. |
311 | 2 | pub async fn keys(&self) -> impl Iterator<Item = (KeyNamespace, [u8; 32])> { _RNvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB4_8Keystore4keys Line | Count | Source | 311 | 2 | pub async fn keys(&self) -> impl Iterator<Item = (KeyNamespace, [u8; 32])> { |
Unexecuted instantiation: _RNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB4_8Keystore4keys |
312 | 2 | let guarded = self.guarded.lock().await0 ; |
313 | 2 | guarded.keys.keys().cloned().collect::<Vec<_>>().into_iter() |
314 | 2 | } _RNCNvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB6_8Keystore4keys0Ba_ Line | Count | Source | 311 | 2 | pub async fn keys(&self) -> impl Iterator<Item = (KeyNamespace, [u8; 32])> { | 312 | 2 | let guarded = self.guarded.lock().await0 ; | 313 | 2 | guarded.keys.keys().cloned().collect::<Vec<_>>().into_iter() | 314 | 2 | } |
Unexecuted instantiation: _RNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB6_8Keystore4keys0Ba_ |
315 | | |
316 | | /// Generates a new Sr25519 key and inserts it in the keystore. |
317 | | /// |
318 | | /// If `save` is `true`, the generated key is saved in the file system. This function returns |
319 | | /// an error only if `save` is `true` and the key couldn't be written to the file system. |
320 | | /// The value of `save` is silently ignored if no path was provided to [`Keystore::new`]. |
321 | | /// |
322 | | /// Returns the corresponding public key. |
323 | 1 | pub async fn generate_sr25519( |
324 | 1 | &self, |
325 | 1 | namespace: KeyNamespace, |
326 | 1 | save: bool, |
327 | 1 | ) -> Result<[u8; 32], io::Error> { _RNvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB4_8Keystore16generate_sr25519 Line | Count | Source | 323 | 1 | pub async fn generate_sr25519( | 324 | 1 | &self, | 325 | 1 | namespace: KeyNamespace, | 326 | 1 | save: bool, | 327 | 1 | ) -> Result<[u8; 32], io::Error> { |
Unexecuted instantiation: _RNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB4_8Keystore16generate_sr25519 |
328 | 1 | let mut guarded = self.guarded.lock().await0 ; |
329 | | |
330 | | // Note: it is in principle possible to generate some entropy from the PRNG, then unlock |
331 | | // the mutex while the private key is being generated. This reduces the time during which |
332 | | // the mutex is locked, but in practice generating a key is a rare enough event that this |
333 | | // is not worth the effort. |
334 | 1 | let mini_secret = zeroize::Zeroizing::new(schnorrkel::MiniSecretKey::generate_with( |
335 | 1 | &mut guarded.gen_rng, |
336 | 1 | )); |
337 | 1 | let keypair = zeroize::Zeroizing::new( |
338 | 1 | mini_secret.expand_to_keypair(schnorrkel::ExpansionMode::Ed25519), |
339 | 1 | ); |
340 | 1 | let public_key = keypair.public.to_bytes(); |
341 | | |
342 | 1 | let save_path = if save { |
343 | 1 | self.path_of_key_sr25519(namespace, &public_key) |
344 | | } else { |
345 | 0 | None |
346 | | }; |
347 | | |
348 | 1 | if let Some(save_path) = save_path { |
349 | 1 | Self::write_to_file_sr25519(&save_path, &mini_secret).await0 ?0 ; |
350 | 1 | guarded |
351 | 1 | .keys |
352 | 1 | .insert((namespace, public_key), PrivateKey::FileSr25519); |
353 | 0 | } else { |
354 | 0 | guarded |
355 | 0 | .keys |
356 | 0 | .insert((namespace, public_key), PrivateKey::MemorySr25519(keypair)); |
357 | 0 | } |
358 | | |
359 | 1 | Ok(public_key) |
360 | 1 | } _RNCNvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB6_8Keystore16generate_sr255190Ba_ Line | Count | Source | 327 | 1 | ) -> Result<[u8; 32], io::Error> { | 328 | 1 | let mut guarded = self.guarded.lock().await0 ; | 329 | | | 330 | | // Note: it is in principle possible to generate some entropy from the PRNG, then unlock | 331 | | // the mutex while the private key is being generated. This reduces the time during which | 332 | | // the mutex is locked, but in practice generating a key is a rare enough event that this | 333 | | // is not worth the effort. | 334 | 1 | let mini_secret = zeroize::Zeroizing::new(schnorrkel::MiniSecretKey::generate_with( | 335 | 1 | &mut guarded.gen_rng, | 336 | 1 | )); | 337 | 1 | let keypair = zeroize::Zeroizing::new( | 338 | 1 | mini_secret.expand_to_keypair(schnorrkel::ExpansionMode::Ed25519), | 339 | 1 | ); | 340 | 1 | let public_key = keypair.public.to_bytes(); | 341 | | | 342 | 1 | let save_path = if save { | 343 | 1 | self.path_of_key_sr25519(namespace, &public_key) | 344 | | } else { | 345 | 0 | None | 346 | | }; | 347 | | | 348 | 1 | if let Some(save_path) = save_path { | 349 | 1 | Self::write_to_file_sr25519(&save_path, &mini_secret).await0 ?0 ; | 350 | 1 | guarded | 351 | 1 | .keys | 352 | 1 | .insert((namespace, public_key), PrivateKey::FileSr25519); | 353 | 0 | } else { | 354 | 0 | guarded | 355 | 0 | .keys | 356 | 0 | .insert((namespace, public_key), PrivateKey::MemorySr25519(keypair)); | 357 | 0 | } | 358 | | | 359 | 1 | Ok(public_key) | 360 | 1 | } |
Unexecuted instantiation: _RNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB6_8Keystore16generate_sr255190Ba_ |
361 | | |
362 | | /// Signs the given payload using the private key associated to the public key passed as |
363 | | /// parameter. |
364 | | /// |
365 | | /// An error is returned if the key-namespace combination is not in the keystore, or if the |
366 | | /// key couldn't be loaded from disk. In the case when a key couldn't be loaded from disk, it |
367 | | /// is automatically removed from the keystore. |
368 | 2 | pub async fn sign( |
369 | 2 | &self, |
370 | 2 | key_namespace: KeyNamespace, |
371 | 2 | public_key: &[u8; 32], |
372 | 2 | payload: &[u8], |
373 | 2 | ) -> Result<[u8; 64], SignError> { _RNvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB4_8Keystore4sign Line | Count | Source | 368 | 2 | pub async fn sign( | 369 | 2 | &self, | 370 | 2 | key_namespace: KeyNamespace, | 371 | 2 | public_key: &[u8; 32], | 372 | 2 | payload: &[u8], | 373 | 2 | ) -> Result<[u8; 64], SignError> { |
Unexecuted instantiation: _RNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB4_8Keystore4sign |
374 | 2 | let mut guarded = self.guarded.lock().await0 ; |
375 | 2 | let key = guarded |
376 | 2 | .keys |
377 | 2 | .get(&(key_namespace, *public_key)) |
378 | 2 | .ok_or(SignError::UnknownPublicKey)?0 ; |
379 | | |
380 | 2 | match key { |
381 | 0 | PrivateKey::MemoryEd25519(key) => Ok(key.sign(payload).into()), |
382 | | PrivateKey::FileEd25519 => { |
383 | 1 | match Self::load_ed25519_from_file( |
384 | 1 | self.path_of_key_ed25519(key_namespace, public_key).unwrap(), |
385 | 1 | ) |
386 | 0 | .await |
387 | | { |
388 | 1 | Ok(key) => { |
389 | 1 | drop(guarded); |
390 | 1 | Ok(key.sign(payload).into()) |
391 | | } |
392 | 0 | Err(err) => { |
393 | 0 | guarded.keys.remove(&(key_namespace, *public_key)); |
394 | 0 | Err(err.into()) |
395 | | } |
396 | | } |
397 | | } |
398 | 0 | PrivateKey::MemorySr25519(key) => Ok(key |
399 | 0 | .sign(self.sr25519_signing_context.bytes(payload)) |
400 | 0 | .to_bytes()), |
401 | | PrivateKey::FileSr25519 => { |
402 | 1 | match Self::load_sr25519_from_file( |
403 | 1 | self.path_of_key_sr25519(key_namespace, public_key).unwrap(), |
404 | 1 | ) |
405 | 0 | .await |
406 | | { |
407 | 1 | Ok(key) => { |
408 | 1 | drop(guarded); |
409 | 1 | Ok(key |
410 | 1 | .sign(self.sr25519_signing_context.bytes(payload)) |
411 | 1 | .to_bytes()) |
412 | | } |
413 | 0 | Err(err) => { |
414 | 0 | guarded.keys.remove(&(key_namespace, *public_key)); |
415 | 0 | Err(err.into()) |
416 | | } |
417 | | } |
418 | | } |
419 | | } |
420 | 2 | } _RNCNvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB6_8Keystore4sign0Ba_ Line | Count | Source | 373 | 2 | ) -> Result<[u8; 64], SignError> { | 374 | 2 | let mut guarded = self.guarded.lock().await0 ; | 375 | 2 | let key = guarded | 376 | 2 | .keys | 377 | 2 | .get(&(key_namespace, *public_key)) | 378 | 2 | .ok_or(SignError::UnknownPublicKey)?0 ; | 379 | | | 380 | 2 | match key { | 381 | 0 | PrivateKey::MemoryEd25519(key) => Ok(key.sign(payload).into()), | 382 | | PrivateKey::FileEd25519 => { | 383 | 1 | match Self::load_ed25519_from_file( | 384 | 1 | self.path_of_key_ed25519(key_namespace, public_key).unwrap(), | 385 | 1 | ) | 386 | 0 | .await | 387 | | { | 388 | 1 | Ok(key) => { | 389 | 1 | drop(guarded); | 390 | 1 | Ok(key.sign(payload).into()) | 391 | | } | 392 | 0 | Err(err) => { | 393 | 0 | guarded.keys.remove(&(key_namespace, *public_key)); | 394 | 0 | Err(err.into()) | 395 | | } | 396 | | } | 397 | | } | 398 | 0 | PrivateKey::MemorySr25519(key) => Ok(key | 399 | 0 | .sign(self.sr25519_signing_context.bytes(payload)) | 400 | 0 | .to_bytes()), | 401 | | PrivateKey::FileSr25519 => { | 402 | 1 | match Self::load_sr25519_from_file( | 403 | 1 | self.path_of_key_sr25519(key_namespace, public_key).unwrap(), | 404 | 1 | ) | 405 | 0 | .await | 406 | | { | 407 | 1 | Ok(key) => { | 408 | 1 | drop(guarded); | 409 | 1 | Ok(key | 410 | 1 | .sign(self.sr25519_signing_context.bytes(payload)) | 411 | 1 | .to_bytes()) | 412 | | } | 413 | 0 | Err(err) => { | 414 | 0 | guarded.keys.remove(&(key_namespace, *public_key)); | 415 | 0 | Err(err.into()) | 416 | | } | 417 | | } | 418 | | } | 419 | | } | 420 | 2 | } |
Unexecuted instantiation: _RNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB6_8Keystore4sign0Ba_ Unexecuted instantiation: _RNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB6_8Keystore4sign0CsiLzmwikkc22_14json_rpc_basic Unexecuted instantiation: _RNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB6_8Keystore4sign0CscDgN54JpMGG_6author Unexecuted instantiation: _RNCNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB6_8Keystore4sign0CsibGXYHQB8Ea_25json_rpc_general_requests |
421 | | |
422 | | // TODO: doc |
423 | | /// |
424 | | /// Note that the labels must be `'static` due to requirements from the underlying library. |
425 | | // TODO: unclear why this can't be an async function; getting lifetime errors |
426 | 0 | pub fn sign_sr25519_vrf<'a>( |
427 | 0 | &'a self, |
428 | 0 | key_namespace: KeyNamespace, |
429 | 0 | public_key: &'a [u8; 32], |
430 | 0 | label: &'static [u8], |
431 | 0 | transcript_items: impl Iterator<Item = (&'static [u8], either::Either<&'a [u8], u64>)> + 'a, |
432 | 0 | ) -> impl core::future::Future<Output = Result<VrfSignature, SignVrfError>> + 'a { |
433 | 0 | async move { |
434 | 0 | let mut guarded = self.guarded.lock().await; |
435 | 0 | let key = guarded |
436 | 0 | .keys |
437 | 0 | .get(&(key_namespace, *public_key)) |
438 | 0 | .ok_or(SignVrfError::Sign(SignError::UnknownPublicKey))?; |
439 | | |
440 | 0 | match key { |
441 | | PrivateKey::MemoryEd25519(_) | PrivateKey::FileEd25519 => { |
442 | 0 | Err(SignVrfError::WrongKeyAlgorithm) |
443 | | } |
444 | | PrivateKey::MemorySr25519(_) | PrivateKey::FileSr25519 => { |
445 | 0 | let key = match key { |
446 | 0 | PrivateKey::MemorySr25519(key) => Cow::Borrowed(key), |
447 | | PrivateKey::FileSr25519 => { |
448 | 0 | match Self::load_sr25519_from_file( |
449 | 0 | self.path_of_key_sr25519(key_namespace, public_key).unwrap(), |
450 | 0 | ) |
451 | 0 | .await |
452 | | { |
453 | 0 | Ok(key) => { |
454 | 0 | drop(guarded); |
455 | 0 | Cow::Owned(key) |
456 | | } |
457 | 0 | Err(err) => { |
458 | 0 | guarded.keys.remove(&(key_namespace, *public_key)); |
459 | 0 | return Err(err.into()); |
460 | | } |
461 | | } |
462 | | } |
463 | 0 | _ => unreachable!(), |
464 | | }; |
465 | | |
466 | 0 | let mut transcript = merlin::Transcript::new(label); |
467 | 0 | for (label, value) in transcript_items { |
468 | 0 | match value { |
469 | 0 | either::Left(bytes) => { |
470 | 0 | transcript.append_message(label, bytes); |
471 | 0 | } |
472 | 0 | either::Right(value) => { |
473 | 0 | transcript.append_u64(label, value); |
474 | 0 | } |
475 | | } |
476 | | } |
477 | | |
478 | 0 | let (_in_out, proof, _) = key.vrf_sign(transcript); |
479 | 0 | Ok(VrfSignature { |
480 | 0 | // TODO: should probably output the `_in_out` as well |
481 | 0 | proof: proof.to_bytes(), |
482 | 0 | }) |
483 | | } |
484 | | } |
485 | 0 | } Unexecuted instantiation: _RNCINvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB7_8Keystore16sign_sr25519_vrfpE0Bb_ Unexecuted instantiation: _RNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB7_8Keystore16sign_sr25519_vrfpE0Bb_ |
486 | 0 | } Unexecuted instantiation: _RINvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB5_8Keystore16sign_sr25519_vrfpEB9_ Unexecuted instantiation: _RINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_8Keystore16sign_sr25519_vrfpEB9_ |
487 | | |
488 | 2 | async fn load_ed25519_from_file( |
489 | 2 | path: impl AsRef<path::Path>, |
490 | 2 | ) -> Result<zeroize::Zeroizing<ed25519_zebra::SigningKey>, KeyLoadError> { _RINvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB5_8Keystore22load_ed25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufEB9_ Line | Count | Source | 488 | 2 | async fn load_ed25519_from_file( | 489 | 2 | path: impl AsRef<path::Path>, | 490 | 2 | ) -> Result<zeroize::Zeroizing<ed25519_zebra::SigningKey>, KeyLoadError> { |
Unexecuted instantiation: _RINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_8Keystore22load_ed25519_from_filepEB9_ Unexecuted instantiation: _RINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_8Keystore22load_ed25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufECsiLzmwikkc22_14json_rpc_basic Unexecuted instantiation: _RINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_8Keystore22load_ed25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufECscDgN54JpMGG_6author Unexecuted instantiation: _RINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_8Keystore22load_ed25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufECsibGXYHQB8Ea_25json_rpc_general_requests |
491 | | // TODO: read asynchronously? |
492 | 2 | let bytes = fs::read(path).map_err(KeyLoadError::Io)?0 ; |
493 | 2 | let phrase = |
494 | 2 | str::from_utf8(&bytes).map_err(|err| KeyLoadError::BadFormat(err.to_string())0 )?0 ; Unexecuted instantiation: _RNCNCINvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB9_8Keystore22load_ed25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE00Bd_ Unexecuted instantiation: _RNCNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB9_8Keystore22load_ed25519_from_filepE00Bd_ Unexecuted instantiation: _RNCNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB9_8Keystore22load_ed25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE00CsiLzmwikkc22_14json_rpc_basic Unexecuted instantiation: _RNCNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB9_8Keystore22load_ed25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE00CscDgN54JpMGG_6author Unexecuted instantiation: _RNCNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB9_8Keystore22load_ed25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE00CsibGXYHQB8Ea_25json_rpc_general_requests |
495 | 2 | let mut private_key = seed_phrase::decode_ed25519_private_key(phrase) |
496 | 2 | .map_err(|err| KeyLoadError::BadFormat(err.to_string())0 )?0 ; Unexecuted instantiation: _RNCNCINvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB9_8Keystore22load_ed25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE0s_0Bd_ Unexecuted instantiation: _RNCNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB9_8Keystore22load_ed25519_from_filepE0s_0Bd_ Unexecuted instantiation: _RNCNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB9_8Keystore22load_ed25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE0s_0CsiLzmwikkc22_14json_rpc_basic Unexecuted instantiation: _RNCNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB9_8Keystore22load_ed25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE0s_0CscDgN54JpMGG_6author Unexecuted instantiation: _RNCNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB9_8Keystore22load_ed25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE0s_0CsibGXYHQB8Ea_25json_rpc_general_requests |
497 | 2 | let zebra_key = zeroize::Zeroizing::new(ed25519_zebra::SigningKey::from(*private_key)); |
498 | 2 | zeroize::Zeroize::zeroize(&mut *private_key); |
499 | 2 | Ok(zebra_key) |
500 | 2 | } _RNCINvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB7_8Keystore22load_ed25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE0Bb_ Line | Count | Source | 490 | 2 | ) -> Result<zeroize::Zeroizing<ed25519_zebra::SigningKey>, KeyLoadError> { | 491 | | // TODO: read asynchronously? | 492 | 2 | let bytes = fs::read(path).map_err(KeyLoadError::Io)?0 ; | 493 | 2 | let phrase = | 494 | 2 | str::from_utf8(&bytes).map_err(|err| KeyLoadError::BadFormat(err.to_string()))?0 ; | 495 | 2 | let mut private_key = seed_phrase::decode_ed25519_private_key(phrase) | 496 | 2 | .map_err(|err| KeyLoadError::BadFormat(err.to_string()))?0 ; | 497 | 2 | let zebra_key = zeroize::Zeroizing::new(ed25519_zebra::SigningKey::from(*private_key)); | 498 | 2 | zeroize::Zeroize::zeroize(&mut *private_key); | 499 | 2 | Ok(zebra_key) | 500 | 2 | } |
Unexecuted instantiation: _RNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB7_8Keystore22load_ed25519_from_filepE0Bb_ Unexecuted instantiation: _RNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB7_8Keystore22load_ed25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE0CsiLzmwikkc22_14json_rpc_basic Unexecuted instantiation: _RNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB7_8Keystore22load_ed25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE0CscDgN54JpMGG_6author Unexecuted instantiation: _RNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB7_8Keystore22load_ed25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE0CsibGXYHQB8Ea_25json_rpc_general_requests |
501 | | |
502 | 2 | async fn load_sr25519_from_file( |
503 | 2 | path: impl AsRef<path::Path>, |
504 | 2 | ) -> Result<zeroize::Zeroizing<schnorrkel::Keypair>, KeyLoadError> { _RINvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB5_8Keystore22load_sr25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufEB9_ Line | Count | Source | 502 | 2 | async fn load_sr25519_from_file( | 503 | 2 | path: impl AsRef<path::Path>, | 504 | 2 | ) -> Result<zeroize::Zeroizing<schnorrkel::Keypair>, KeyLoadError> { |
Unexecuted instantiation: _RINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_8Keystore22load_sr25519_from_filepEB9_ Unexecuted instantiation: _RINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_8Keystore22load_sr25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufECsiLzmwikkc22_14json_rpc_basic Unexecuted instantiation: _RINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_8Keystore22load_sr25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufECscDgN54JpMGG_6author Unexecuted instantiation: _RINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_8Keystore22load_sr25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufECsibGXYHQB8Ea_25json_rpc_general_requests |
505 | | // TODO: read asynchronously? |
506 | 2 | let bytes = fs::read(path).map_err(KeyLoadError::Io)?0 ; |
507 | 2 | let phrase = |
508 | 2 | str::from_utf8(&bytes).map_err(|err| KeyLoadError::BadFormat(err.to_string())0 )?0 ; Unexecuted instantiation: _RNCNCINvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB9_8Keystore22load_sr25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE00Bd_ Unexecuted instantiation: _RNCNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB9_8Keystore22load_sr25519_from_filepE00Bd_ Unexecuted instantiation: _RNCNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB9_8Keystore22load_sr25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE00CsiLzmwikkc22_14json_rpc_basic Unexecuted instantiation: _RNCNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB9_8Keystore22load_sr25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE00CscDgN54JpMGG_6author Unexecuted instantiation: _RNCNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB9_8Keystore22load_sr25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE00CsibGXYHQB8Ea_25json_rpc_general_requests |
509 | 2 | let mut private_key = seed_phrase::decode_sr25519_private_key(phrase) |
510 | 2 | .map_err(|err| KeyLoadError::BadFormat(err.to_string())0 )?0 ; Unexecuted instantiation: _RNCNCINvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB9_8Keystore22load_sr25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE0s_0Bd_ Unexecuted instantiation: _RNCNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB9_8Keystore22load_sr25519_from_filepE0s_0Bd_ Unexecuted instantiation: _RNCNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB9_8Keystore22load_sr25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE0s_0CsiLzmwikkc22_14json_rpc_basic Unexecuted instantiation: _RNCNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB9_8Keystore22load_sr25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE0s_0CscDgN54JpMGG_6author Unexecuted instantiation: _RNCNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB9_8Keystore22load_sr25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE0s_0CsibGXYHQB8Ea_25json_rpc_general_requests |
511 | | // `from_bytes` only panics if the key is of the wrong length, which we know can't |
512 | | // happen here. |
513 | 2 | let schnorrkel_key = zeroize::Zeroizing::new( |
514 | 2 | schnorrkel::SecretKey::from_bytes(&*private_key) |
515 | 2 | .unwrap() |
516 | 2 | .into(), |
517 | 2 | ); |
518 | 2 | zeroize::Zeroize::zeroize(&mut *private_key); |
519 | 2 | Ok(schnorrkel_key) |
520 | 2 | } _RNCINvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB7_8Keystore22load_sr25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE0Bb_ Line | Count | Source | 504 | 2 | ) -> Result<zeroize::Zeroizing<schnorrkel::Keypair>, KeyLoadError> { | 505 | | // TODO: read asynchronously? | 506 | 2 | let bytes = fs::read(path).map_err(KeyLoadError::Io)?0 ; | 507 | 2 | let phrase = | 508 | 2 | str::from_utf8(&bytes).map_err(|err| KeyLoadError::BadFormat(err.to_string()))?0 ; | 509 | 2 | let mut private_key = seed_phrase::decode_sr25519_private_key(phrase) | 510 | 2 | .map_err(|err| KeyLoadError::BadFormat(err.to_string()))?0 ; | 511 | | // `from_bytes` only panics if the key is of the wrong length, which we know can't | 512 | | // happen here. | 513 | 2 | let schnorrkel_key = zeroize::Zeroizing::new( | 514 | 2 | schnorrkel::SecretKey::from_bytes(&*private_key) | 515 | 2 | .unwrap() | 516 | 2 | .into(), | 517 | 2 | ); | 518 | 2 | zeroize::Zeroize::zeroize(&mut *private_key); | 519 | 2 | Ok(schnorrkel_key) | 520 | 2 | } |
Unexecuted instantiation: _RNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB7_8Keystore22load_sr25519_from_filepE0Bb_ Unexecuted instantiation: _RNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB7_8Keystore22load_sr25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE0CsiLzmwikkc22_14json_rpc_basic Unexecuted instantiation: _RNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB7_8Keystore22load_sr25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE0CscDgN54JpMGG_6author Unexecuted instantiation: _RNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB7_8Keystore22load_sr25519_from_fileNtNtCsbpXXxgr6u8g_3std4path7PathBufE0CsibGXYHQB8Ea_25json_rpc_general_requests |
521 | | |
522 | 1 | async fn write_to_file_ed25519( |
523 | 1 | path: impl AsRef<path::Path>, |
524 | 1 | key: &ed25519_zebra::SigningKey, |
525 | 1 | ) -> Result<(), io::Error> { _RINvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB5_8Keystore21write_to_file_ed25519RNtNtCsbpXXxgr6u8g_3std4path7PathBufEB9_ Line | Count | Source | 522 | 1 | async fn write_to_file_ed25519( | 523 | 1 | path: impl AsRef<path::Path>, | 524 | 1 | key: &ed25519_zebra::SigningKey, | 525 | 1 | ) -> Result<(), io::Error> { |
Unexecuted instantiation: _RINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_8Keystore21write_to_file_ed25519pEB9_ |
526 | 1 | let mut phrase = zeroize::Zeroizing::new(vec![0; key.as_ref().len() * 2]); |
527 | 1 | hex::encode_to_slice(key.as_ref(), &mut phrase).unwrap(); |
528 | 1 | Self::write_to_file(path, &phrase).await0 |
529 | 1 | } _RNCINvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB7_8Keystore21write_to_file_ed25519RNtNtCsbpXXxgr6u8g_3std4path7PathBufE0Bb_ Line | Count | Source | 525 | 1 | ) -> Result<(), io::Error> { | 526 | 1 | let mut phrase = zeroize::Zeroizing::new(vec![0; key.as_ref().len() * 2]); | 527 | 1 | hex::encode_to_slice(key.as_ref(), &mut phrase).unwrap(); | 528 | 1 | Self::write_to_file(path, &phrase).await0 | 529 | 1 | } |
Unexecuted instantiation: _RNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB7_8Keystore21write_to_file_ed25519pE0Bb_ |
530 | | |
531 | 1 | async fn write_to_file_sr25519( |
532 | 1 | path: impl AsRef<path::Path>, |
533 | 1 | key: &schnorrkel::MiniSecretKey, |
534 | 1 | ) -> Result<(), io::Error> { _RINvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB5_8Keystore21write_to_file_sr25519RNtNtCsbpXXxgr6u8g_3std4path7PathBufEB9_ Line | Count | Source | 531 | 1 | async fn write_to_file_sr25519( | 532 | 1 | path: impl AsRef<path::Path>, | 533 | 1 | key: &schnorrkel::MiniSecretKey, | 534 | 1 | ) -> Result<(), io::Error> { |
Unexecuted instantiation: _RINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_8Keystore21write_to_file_sr25519pEB9_ |
535 | 1 | // TODO: `to_bytes` isn't zeroize-friendly |
536 | 1 | let bytes = key.to_bytes(); |
537 | 1 | let mut phrase = zeroize::Zeroizing::new(vec![0; bytes.len() * 2]); |
538 | 1 | hex::encode_to_slice(bytes, &mut phrase).unwrap(); |
539 | 1 | Self::write_to_file(path, &phrase).await0 |
540 | 1 | } _RNCINvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB7_8Keystore21write_to_file_sr25519RNtNtCsbpXXxgr6u8g_3std4path7PathBufE0Bb_ Line | Count | Source | 534 | 1 | ) -> Result<(), io::Error> { | 535 | 1 | // TODO: `to_bytes` isn't zeroize-friendly | 536 | 1 | let bytes = key.to_bytes(); | 537 | 1 | let mut phrase = zeroize::Zeroizing::new(vec![0; bytes.len() * 2]); | 538 | 1 | hex::encode_to_slice(bytes, &mut phrase).unwrap(); | 539 | 1 | Self::write_to_file(path, &phrase).await0 | 540 | 1 | } |
Unexecuted instantiation: _RNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB7_8Keystore21write_to_file_sr25519pE0Bb_ |
541 | | |
542 | 2 | async fn write_to_file( |
543 | 2 | path: impl AsRef<path::Path>, |
544 | 2 | key_phrase: &[u8], |
545 | 2 | ) -> Result<(), io::Error> { _RINvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB5_8Keystore13write_to_fileRNtNtCsbpXXxgr6u8g_3std4path7PathBufEB9_ Line | Count | Source | 542 | 2 | async fn write_to_file( | 543 | 2 | path: impl AsRef<path::Path>, | 544 | 2 | key_phrase: &[u8], | 545 | 2 | ) -> Result<(), io::Error> { |
Unexecuted instantiation: _RINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_8Keystore13write_to_filepEB9_ |
546 | 2 | let mut file = fs::File::create(path)?0 ; |
547 | | // TODO: proper security flags on Windows? |
548 | | #[cfg(target_family = "unix")] |
549 | 2 | file.set_permissions(std::os::unix::fs::PermissionsExt::from_mode(0o400))?0 ; |
550 | 2 | io::Write::write_all(&mut file, b"0x")?0 ; |
551 | 2 | io::Write::write_all(&mut file, key_phrase)?0 ; |
552 | 2 | io::Write::flush(&mut file)?0 ; // This call is generally useless, but doesn't hurt. |
553 | 2 | file.sync_all()?0 ; |
554 | 2 | Ok(()) |
555 | 2 | } _RNCINvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB7_8Keystore13write_to_fileRNtNtCsbpXXxgr6u8g_3std4path7PathBufE0Bb_ Line | Count | Source | 545 | 2 | ) -> Result<(), io::Error> { | 546 | 2 | let mut file = fs::File::create(path)?0 ; | 547 | | // TODO: proper security flags on Windows? | 548 | | #[cfg(target_family = "unix")] | 549 | 2 | file.set_permissions(std::os::unix::fs::PermissionsExt::from_mode(0o400))?0 ; | 550 | 2 | io::Write::write_all(&mut file, b"0x")?0 ; | 551 | 2 | io::Write::write_all(&mut file, key_phrase)?0 ; | 552 | 2 | io::Write::flush(&mut file)?0 ; // This call is generally useless, but doesn't hurt. | 553 | 2 | file.sync_all()?0 ; | 554 | 2 | Ok(()) | 555 | 2 | } |
Unexecuted instantiation: _RNCINvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB7_8Keystore13write_to_filepE0Bb_ |
556 | | |
557 | 2 | fn path_of_key_ed25519( |
558 | 2 | &self, |
559 | 2 | key_namespace: KeyNamespace, |
560 | 2 | public_key: &[u8; 32], |
561 | 2 | ) -> Option<path::PathBuf> { |
562 | 2 | self.path_of_key(key_namespace, "ed25519", public_key) |
563 | 2 | } _RNvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB4_8Keystore19path_of_key_ed25519 Line | Count | Source | 557 | 2 | fn path_of_key_ed25519( | 558 | 2 | &self, | 559 | 2 | key_namespace: KeyNamespace, | 560 | 2 | public_key: &[u8; 32], | 561 | 2 | ) -> Option<path::PathBuf> { | 562 | 2 | self.path_of_key(key_namespace, "ed25519", public_key) | 563 | 2 | } |
Unexecuted instantiation: _RNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB4_8Keystore19path_of_key_ed25519 |
564 | | |
565 | 2 | fn path_of_key_sr25519( |
566 | 2 | &self, |
567 | 2 | key_namespace: KeyNamespace, |
568 | 2 | public_key: &[u8; 32], |
569 | 2 | ) -> Option<path::PathBuf> { |
570 | 2 | self.path_of_key(key_namespace, "sr25519", public_key) |
571 | 2 | } _RNvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB4_8Keystore19path_of_key_sr25519 Line | Count | Source | 565 | 2 | fn path_of_key_sr25519( | 566 | 2 | &self, | 567 | 2 | key_namespace: KeyNamespace, | 568 | 2 | public_key: &[u8; 32], | 569 | 2 | ) -> Option<path::PathBuf> { | 570 | 2 | self.path_of_key(key_namespace, "sr25519", public_key) | 571 | 2 | } |
Unexecuted instantiation: _RNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB4_8Keystore19path_of_key_sr25519 |
572 | | |
573 | 4 | fn path_of_key( |
574 | 4 | &self, |
575 | 4 | key_namespace: KeyNamespace, |
576 | 4 | key_algorithm: &str, |
577 | 4 | public_key: &[u8; 32], |
578 | 4 | ) -> Option<path::PathBuf> { |
579 | 4 | let keys_directory = match &self.keys_directory { |
580 | 4 | Some(k) => k, |
581 | 0 | None => return None, |
582 | | }; |
583 | | |
584 | | // We don't use the same pathing scheme as Substrate, for two reasons: |
585 | | // - The fact that Substrate hex-encodes the namespace is completely unnecessary and |
586 | | // confusing. |
587 | | // - Substrate doesn't indicate whether the key is ed25519 or sr25519, because the |
588 | | // algorithm to use is provided when signing or verifying. This is weird and in my opinion |
589 | | // not a good practice. |
590 | | |
591 | 4 | let mut file_name = String::with_capacity(256); // 256 is more than enough. |
592 | 4 | file_name.push_str(key_namespace.as_string()); |
593 | 4 | file_name.push('-'); |
594 | 4 | file_name.push_str(key_algorithm); |
595 | 4 | file_name.push('-'); |
596 | 4 | file_name.push_str(&hex::encode(public_key)); |
597 | 4 | |
598 | 4 | let mut path = |
599 | 4 | path::PathBuf::with_capacity(keys_directory.as_os_str().len() + file_name.len() + 16); |
600 | 4 | path.push(keys_directory); |
601 | 4 | path.push(file_name); |
602 | 4 | Some(path) |
603 | 4 | } _RNvMs_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB4_8Keystore11path_of_key Line | Count | Source | 573 | 4 | fn path_of_key( | 574 | 4 | &self, | 575 | 4 | key_namespace: KeyNamespace, | 576 | 4 | key_algorithm: &str, | 577 | 4 | public_key: &[u8; 32], | 578 | 4 | ) -> Option<path::PathBuf> { | 579 | 4 | let keys_directory = match &self.keys_directory { | 580 | 4 | Some(k) => k, | 581 | 0 | None => return None, | 582 | | }; | 583 | | | 584 | | // We don't use the same pathing scheme as Substrate, for two reasons: | 585 | | // - The fact that Substrate hex-encodes the namespace is completely unnecessary and | 586 | | // confusing. | 587 | | // - Substrate doesn't indicate whether the key is ed25519 or sr25519, because the | 588 | | // algorithm to use is provided when signing or verifying. This is weird and in my opinion | 589 | | // not a good practice. | 590 | | | 591 | 4 | let mut file_name = String::with_capacity(256); // 256 is more than enough. | 592 | 4 | file_name.push_str(key_namespace.as_string()); | 593 | 4 | file_name.push('-'); | 594 | 4 | file_name.push_str(key_algorithm); | 595 | 4 | file_name.push('-'); | 596 | 4 | file_name.push_str(&hex::encode(public_key)); | 597 | 4 | | 598 | 4 | let mut path = | 599 | 4 | path::PathBuf::with_capacity(keys_directory.as_os_str().len() + file_name.len() + 16); | 600 | 4 | path.push(keys_directory); | 601 | 4 | path.push(file_name); | 602 | 4 | Some(path) | 603 | 4 | } |
Unexecuted instantiation: _RNvMs_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB4_8Keystore11path_of_key |
604 | | } |
605 | | |
606 | | struct Guarded { |
607 | | gen_rng: rand_chacha::ChaCha20Rng, |
608 | | keys: hashbrown::HashMap<(KeyNamespace, [u8; 32]), PrivateKey, SipHasherBuild>, |
609 | | } |
610 | | |
611 | | pub struct VrfSignature { |
612 | | pub proof: [u8; 64], |
613 | | } |
614 | | |
615 | 0 | #[derive(Debug, derive_more::Display)] Unexecuted instantiation: _RNvXsa_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB5_9SignErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt Unexecuted instantiation: _RNvXsa_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_9SignErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt |
616 | | pub enum SignError { |
617 | | /// The given `(namespace, public key)` combination is unknown to this keystore. |
618 | | UnknownPublicKey, |
619 | | |
620 | | /// Error while accessing the file containing the secret key. |
621 | | /// Typically indicates the content of the file has been modified by something else than |
622 | | /// the keystore. |
623 | | #[display(fmt = "Error loading the secret key; {_0}")] |
624 | | KeyLoad(KeyLoadError), |
625 | | } |
626 | | |
627 | 0 | #[derive(Debug, derive_more::Display)] Unexecuted instantiation: _RNvXsc_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB5_12KeyLoadErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt Unexecuted instantiation: _RNvXsc_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_12KeyLoadErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt |
628 | | pub enum KeyLoadError { |
629 | | /// Error reported by the operating system. |
630 | | #[display(fmt = "{_0}")] |
631 | | Io(io::Error), |
632 | | /// Content of the file is invalid. Contains a human-readable error message as a string. |
633 | | /// Because the format of the content of the file is an implementation detail, no detail is |
634 | | /// provided. |
635 | | #[display(fmt = "{_0}")] |
636 | | BadFormat(String), |
637 | | } |
638 | | |
639 | 0 | #[derive(Debug, derive_more::Display)] Unexecuted instantiation: _RNvXse_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB5_12SignVrfErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt Unexecuted instantiation: _RNvXse_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_12SignVrfErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt |
640 | | pub enum SignVrfError { |
641 | | #[display(fmt = "{_0}")] |
642 | | Sign(SignError), |
643 | | WrongKeyAlgorithm, |
644 | | } |
645 | | |
646 | | enum PrivateKey { |
647 | | MemoryEd25519(zeroize::Zeroizing<ed25519_zebra::SigningKey>), |
648 | | MemorySr25519(zeroize::Zeroizing<schnorrkel::Keypair>), |
649 | | FileEd25519, |
650 | | FileSr25519, |
651 | | } |
652 | | |
653 | | impl From<KeyLoadError> for SignError { |
654 | 0 | fn from(err: KeyLoadError) -> SignError { |
655 | 0 | SignError::KeyLoad(err) |
656 | 0 | } Unexecuted instantiation: _RNvXs0_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB5_9SignErrorINtNtCsaYZPK01V26L_4core7convert4FromNtB5_12KeyLoadErrorE4from Unexecuted instantiation: _RNvXs0_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_9SignErrorINtNtCsaYZPK01V26L_4core7convert4FromNtB5_12KeyLoadErrorE4from |
657 | | } |
658 | | |
659 | | impl From<KeyLoadError> for SignVrfError { |
660 | 0 | fn from(err: KeyLoadError) -> SignVrfError { |
661 | 0 | SignVrfError::Sign(SignError::KeyLoad(err)) |
662 | 0 | } Unexecuted instantiation: _RNvXs1_NtNtCsN16ciHI6Qf_7smoldot8identity8keystoreNtB5_12SignVrfErrorINtNtCsaYZPK01V26L_4core7convert4FromNtB5_12KeyLoadErrorE4from Unexecuted instantiation: _RNvXs1_NtNtCseuYC0Zibziv_7smoldot8identity8keystoreNtB5_12SignVrfErrorINtNtCsaYZPK01V26L_4core7convert4FromNtB5_12KeyLoadErrorE4from |
663 | | } |
664 | | |
665 | | #[cfg(test)] |
666 | | mod tests { |
667 | | use super::{KeyNamespace, Keystore}; |
668 | | |
669 | | #[test] |
670 | 1 | fn disk_storage_works_ed25519() { |
671 | 1 | futures_executor::block_on(async move { |
672 | 1 | let path = tempfile::tempdir().unwrap(); |
673 | | |
674 | 1 | let keystore1 = Keystore::new(Some(path.path().to_owned()), rand::random()) |
675 | 0 | .await |
676 | 1 | .unwrap(); |
677 | 1 | let public_key = keystore1 |
678 | 1 | .generate_ed25519(KeyNamespace::Babe, true) |
679 | 0 | .await |
680 | 1 | .unwrap(); |
681 | 1 | drop(keystore1); |
682 | | |
683 | 1 | let keystore2 = Keystore::new(Some(path.path().to_owned()), rand::random()) |
684 | 0 | .await |
685 | 1 | .unwrap(); |
686 | 1 | assert_eq!( |
687 | 1 | keystore2.keys().await0 .next(), |
688 | 1 | Some((KeyNamespace::Babe, public_key)) |
689 | | ); |
690 | | |
691 | 1 | let signature = keystore2 |
692 | 1 | .sign(KeyNamespace::Babe, &public_key, b"hello world") |
693 | 0 | .await |
694 | 1 | .unwrap(); |
695 | 1 | |
696 | 1 | assert!(ed25519_zebra::VerificationKey::try_from(public_key) |
697 | 1 | .unwrap() |
698 | 1 | .verify(&ed25519_zebra::Signature::from(signature), b"hello world") |
699 | 1 | .is_ok()); |
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 | 0 | .await |
710 | 1 | .unwrap(); |
711 | 1 | let public_key = keystore1 |
712 | 1 | .generate_sr25519(KeyNamespace::Aura, true) |
713 | 0 | .await |
714 | 1 | .unwrap(); |
715 | 1 | drop(keystore1); |
716 | | |
717 | 1 | let keystore2 = Keystore::new(Some(path.path().to_owned()), rand::random()) |
718 | 0 | .await |
719 | 1 | .unwrap(); |
720 | 1 | assert_eq!( |
721 | 1 | keystore2.keys().await0 .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 | 0 | .await |
728 | 1 | .unwrap(); |
729 | 1 | |
730 | 1 | assert!(schnorrkel::PublicKey::from_bytes(&public_key) |
731 | 1 | .unwrap() |
732 | 1 | .verify_simple( |
733 | 1 | b"substrate", |
734 | 1 | b"hello world", |
735 | 1 | &schnorrkel::Signature::from_bytes(&signature).unwrap() |
736 | 1 | ) |
737 | 1 | .is_ok()); |
738 | 1 | }); |
739 | 1 | } |
740 | | } |