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