tempo_consensus_config/
lib.rs1#![cfg_attr(not(test), warn(unused_crate_dependencies))]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5
6use std::{
7 fmt::Display,
8 io::{Read as _, Write as _},
9 path::Path,
10};
11
12use commonware_codec::{DecodeExt as _, Encode as _, FixedSize as _, Write as CodecWrite};
13use commonware_cryptography::{
14 Signer,
15 bls12381::primitives::group::Share,
16 ed25519::{PrivateKey, PublicKey},
17};
18use commonware_math::algebra::Random as _;
19use rand_core::CryptoRngCore;
20use secrecy::{ExposeSecret as _, ExposeSecretMut as _, SecretBox, SecretString};
21
22#[cfg(test)]
23mod tests;
24
25pub type SigningKeyPassphrase = SecretString;
26
27pub const MAX_SIGNING_KEY_PASSPHRASE_BYTES: u64 = 1024;
28
29pub fn read_secret<P: AsRef<Path>>(path: P) -> std::io::Result<(SigningKeyPassphrase, bool)> {
33 use std::os::unix::fs::FileTypeExt as _;
34
35 let file = std::fs::File::open(path)?;
36 let is_fifo = file.metadata()?.file_type().is_fifo();
37
38 Ok((read_secret_inner(file)?, is_fifo))
39}
40
41fn read_secret_inner<R: std::io::Read>(reader: R) -> std::io::Result<SigningKeyPassphrase> {
42 let mut reader = reader;
43 let mut read_result = Ok(());
44 let mut passphrase = SecretBox::init_with_mut(|buf: &mut String| {
45 buf.reserve_exact((MAX_SIGNING_KEY_PASSPHRASE_BYTES + 1) as usize);
46
47 let mut reader =
48 std::io::BufReader::new(&mut reader).take(MAX_SIGNING_KEY_PASSPHRASE_BYTES + 1);
49 read_result = reader.read_to_string(buf).map(|_| ());
50 if read_result.is_err() {
51 return;
52 }
53
54 if buf.len() as u64 > MAX_SIGNING_KEY_PASSPHRASE_BYTES {
55 read_result = Err(std::io::Error::new(
56 std::io::ErrorKind::InvalidData,
57 format!("passphrase exceeds {MAX_SIGNING_KEY_PASSPHRASE_BYTES} byte limit"),
58 ));
59 return;
60 }
61
62 while matches!(buf.as_bytes().last(), Some(b'\r' | b'\n')) {
63 buf.pop();
64 }
65 });
66
67 read_result?;
68
69 Ok(SecretString::from(std::mem::take(
72 passphrase.expose_secret_mut(),
73 )))
74}
75
76#[derive(Clone, Debug)]
77pub struct SigningKey {
78 inner: PrivateKey,
79}
80
81impl SigningKey {
82 pub fn into_inner(self) -> PrivateKey {
83 self.inner
84 }
85
86 pub fn random<R: CryptoRngCore>(rng: R) -> Self {
88 Self {
89 inner: PrivateKey::random(rng),
90 }
91 }
92
93 pub fn read_from_file_unencrypted<P: AsRef<Path>>(path: P) -> Result<Self, SigningKeyError> {
94 let hex = std::fs::read_to_string(path).map_err(SigningKeyErrorKind::Read)?;
95 Self::try_from_hex(hex.trim())
96 }
97
98 pub fn read_from_file_encrypted<P: AsRef<Path>>(
107 path: P,
108 passphrase: SecretString,
109 ) -> Result<Self, SigningKeyError> {
110 let file = std::fs::File::open(path).map_err(SigningKeyErrorKind::Read)?;
111 Self::read_encrypted(std::io::BufReader::new(file), passphrase)
112 }
113
114 pub fn read_encrypted<R: std::io::BufRead>(
117 ciphertext: R,
118 passphrase: SecretString,
119 ) -> Result<Self, SigningKeyError> {
120 let decryptor =
121 age::Decryptor::new_buffered(ciphertext).map_err(SigningKeyErrorKind::Decrypt)?;
122 let identity = age::scrypt::Identity::new(passphrase);
123
124 let mut reader = decryptor
125 .decrypt(std::iter::once(&identity as &dyn age::Identity))
126 .map_err(SigningKeyErrorKind::Decrypt)?;
127
128 let mut io_err: Option<std::io::Error> = None;
129 let plaintext: SecretBox<[u8; PrivateKey::SIZE]> =
130 SecretBox::init_with_mut(|buf: &mut [u8; PrivateKey::SIZE]| {
131 io_err = reader.read_exact(buf).err()
132 });
133 if let Some(err) = io_err {
134 return Err(SigningKeyErrorKind::Read(err).into());
135 }
136
137 let inner = PrivateKey::decode(plaintext.expose_secret().as_ref())
138 .map_err(SigningKeyErrorKind::Parse)?;
139 Ok(Self { inner })
140 }
141
142 pub fn write_to_file_encrypted<P: AsRef<Path>>(
144 &self,
145 path: P,
146 passphrase: SecretString,
147 ) -> Result<(), SigningKeyError> {
148 let file = std::fs::File::create(path).map_err(SigningKeyErrorKind::Write)?;
149 self.write_encrypted(file, passphrase)
150 }
151
152 pub fn write_encrypted<W: std::io::Write>(
155 &self,
156 writer: W,
157 passphrase: SecretString,
158 ) -> Result<(), SigningKeyError> {
159 let plaintext: SecretBox<[u8; PrivateKey::SIZE]> =
162 SecretBox::init_with_mut(|buf: &mut [u8; PrivateKey::SIZE]| {
163 let mut tail: &mut [u8] = buf;
164 CodecWrite::write(&self.inner, &mut tail);
165 });
166
167 let mut age_writer = age::Encryptor::with_user_passphrase(passphrase)
168 .wrap_output(writer)
169 .map_err(SigningKeyErrorKind::Write)?;
170 age_writer
171 .write_all(plaintext.expose_secret())
172 .map_err(SigningKeyErrorKind::Write)?;
173 age_writer.finish().map_err(SigningKeyErrorKind::Write)?;
174
175 Ok(())
176 }
177
178 pub fn try_from_hex(hex: &str) -> Result<Self, SigningKeyError> {
179 let bytes = const_hex::decode(hex).map_err(SigningKeyErrorKind::Hex)?;
180 let inner = PrivateKey::decode(&bytes[..]).map_err(SigningKeyErrorKind::Parse)?;
181 Ok(Self { inner })
182 }
183
184 pub fn to_writer_unencrypted<W: std::io::Write>(
187 &self,
188 mut writer: W,
189 ) -> Result<(), SigningKeyError> {
190 let hex = const_hex::encode_prefixed(self.inner.encode().as_ref());
191 writer
192 .write_all(hex.as_bytes())
193 .map_err(SigningKeyErrorKind::Write)?;
194 Ok(())
195 }
196
197 pub fn public_key(&self) -> PublicKey {
198 self.inner.public_key()
199 }
200}
201
202impl From<PrivateKey> for SigningKey {
203 fn from(inner: PrivateKey) -> Self {
204 Self { inner }
205 }
206}
207
208#[derive(Debug, thiserror::Error)]
209#[error(transparent)]
210pub struct SigningKeyError {
211 #[from]
212 inner: SigningKeyErrorKind,
213}
214
215#[derive(Debug, thiserror::Error)]
216enum SigningKeyErrorKind {
217 #[error("failed decoding file contents as hex-encoded bytes")]
218 Hex(#[source] const_hex::FromHexError),
219 #[error("failed parsing hex-decoded bytes as ed25519 private key")]
220 Parse(#[source] commonware_codec::Error),
221 #[error("failed reading file")]
222 Read(#[source] std::io::Error),
223 #[error("failed writing to file")]
224 Write(#[source] std::io::Error),
225 #[error("failed decrypting age payload (wrong passphrase or malformed file?)")]
226 Decrypt(#[source] age::DecryptError),
227}
228
229#[derive(Clone, Debug, PartialEq, Eq)]
230pub struct SigningShare {
231 inner: Share,
232}
233
234impl SigningShare {
235 pub fn into_inner(self) -> Share {
236 self.inner
237 }
238
239 pub fn read_from_file<P: AsRef<Path>>(path: P) -> Result<Self, SigningShareError> {
240 let hex = std::fs::read_to_string(path).map_err(SigningShareErrorKind::Read)?;
241 Self::try_from_hex(hex.trim())
242 }
243
244 pub fn try_from_hex(hex: &str) -> Result<Self, SigningShareError> {
245 let bytes = const_hex::decode(hex).map_err(SigningShareErrorKind::Hex)?;
246 let inner = Share::decode(&bytes[..]).map_err(SigningShareErrorKind::Parse)?;
247 Ok(Self { inner })
248 }
249
250 pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), SigningShareError> {
251 std::fs::write(path, self.to_string()).map_err(SigningShareErrorKind::Write)?;
252 Ok(())
253 }
254}
255
256impl Display for SigningShare {
257 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258 f.write_str(&const_hex::encode_prefixed(self.inner.encode().as_ref()))
259 }
260}
261
262impl From<Share> for SigningShare {
263 fn from(inner: Share) -> Self {
264 Self { inner }
265 }
266}
267
268#[derive(Debug, thiserror::Error)]
269#[error(transparent)]
270pub struct SigningShareError {
271 #[from]
272 inner: SigningShareErrorKind,
273}
274
275#[derive(Debug, thiserror::Error)]
276enum SigningShareErrorKind {
277 #[error("failed decoding file contents as hex-encoded bytes")]
278 Hex(#[source] const_hex::FromHexError),
279 #[error("failed parsing hex-decoded bytes as bls12381 private share")]
280 Parse(#[source] commonware_codec::Error),
281 #[error("failed reading file")]
282 Read(#[source] std::io::Error),
283 #[error("failed writing to file")]
284 Write(#[source] std::io::Error),
285}