Skip to content

Commit 7554bc1

Browse files
authored
mics: keygen + infra (#13)
- Most error types are never asserted, change to anyhow::Result accordingly. - Use rust-toolchain "1.84.0" - Add cli keygen - Remove error types from energon, they are not finalized yet.
1 parent 54f1c51 commit 7554bc1

14 files changed

+207
-78
lines changed

Diff for: Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ rocksdb = { version = "0.23.0", optional = true, default-features = false, featu
3333
"lz4",
3434
] }
3535
home = "0.5.11"
36+
anyhow = "1.0.95"
3637

3738
[build-dependencies]
3839
tonic-build = "0.12.3"

Diff for: rust-toolchain.toml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[toolchain]
2+
channel = "1.84.0"

Diff for: src/cli.rs

+72-7
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,34 @@
1+
use crate::core::beacon;
2+
use crate::key::keys::Pair;
3+
use crate::key::store::FileStore;
4+
use crate::key::Scheme;
5+
use crate::net::control;
6+
use crate::net::utils::Address;
7+
8+
use anyhow::bail;
9+
use anyhow::Result;
110
use clap::arg;
211
use clap::command;
312
use clap::Parser;
13+
use energon::drand::schemes::*;
414

5-
/// Top-level error for CLI commands
6-
#[derive(thiserror::Error, Debug)]
7-
pub enum CliError {
8-
#[error("could not initialize a logger")]
9-
LogInitError,
15+
/// Generate the long-term keypair (drand.private, drand.public) for this node, and load it on the drand daemon if it is up and running
16+
#[derive(Debug, Parser, Clone)]
17+
pub struct KeyGenConfig {
18+
/// Set the port you want to listen to for control port commands. If not specified, we will use the default value.
19+
#[arg(long, default_value = control::DEFAULT_CONTROL_PORT)]
20+
pub control: String,
21+
/// Folder to keep all drand cryptographic information, with absolute path.
22+
#[arg(long, default_value_t = FileStore::drand_home())]
23+
pub folder: String,
24+
/// Indicates the id for the randomness generation process which the command applies to.
25+
#[arg(long, default_value = beacon::DEFAULT_BEACON_ID)]
26+
pub id: String,
27+
/// Indicates a set of values drand will use to configure the randomness generation process
28+
#[arg(long, default_value = DefaultScheme::ID)]
29+
pub scheme: String,
30+
/// The address other nodes will be able to contact this node on (specified as 'private-listen' to the daemon)
31+
pub address: String,
1032
}
1133

1234
#[derive(Debug, Parser)]
@@ -20,14 +42,57 @@ pub struct CLI {
2042
}
2143

2244
#[derive(Debug, Parser)]
23-
pub enum Cmd {}
45+
pub enum Cmd {
46+
GenerateKeypair(KeyGenConfig),
47+
}
2448

2549
impl CLI {
26-
pub async fn run(self) -> Result<(), CliError> {
50+
pub async fn run(self) -> Result<()> {
2751
// Logs are disabled in tests by default.
2852
#[cfg(not(test))]
2953
crate::log::init_log(self.verbose)?;
3054

55+
match self.commands {
56+
Cmd::GenerateKeypair(config) => keygen_cmd(&config).await?,
57+
}
58+
3159
Ok(())
3260
}
3361
}
62+
63+
async fn keygen_cmd(config: &KeyGenConfig) -> Result<()> {
64+
println!("Generating private / public key pair");
65+
match config.scheme.as_str() {
66+
DefaultScheme::ID => keygen::<DefaultScheme>(config).await?,
67+
UnchainedScheme::ID => keygen::<UnchainedScheme>(config).await?,
68+
SigsOnG1Scheme::ID => keygen::<SigsOnG1Scheme>(config).await?,
69+
BN254UnchainedOnG1Scheme::ID => keygen::<BN254UnchainedOnG1Scheme>(config).await?,
70+
_ => bail!("keygen: unknown scheme: {}", config.scheme),
71+
}
72+
73+
// Note: Loading keys into the daemon at `keygen_cmd` is disabled in tests.
74+
// Testing [`ControlClient::load_beacon`] should be done outside this function.
75+
#[cfg(not(test))]
76+
// If keys were generated successfully, daemon needs to load them.
77+
match control::ControlClient::new(&config.control).await {
78+
Ok(mut client) => {
79+
client.load_beacon(&config.id).await?;
80+
}
81+
Err(_) => {
82+
// Just print, exit code 0
83+
eprintln!("Keys couldn't be loaded on drand daemon. If it is not running, these new keys will be loaded on startup")
84+
}
85+
}
86+
87+
Ok(())
88+
}
89+
90+
/// Generic helper for [`keygen_cmd`]
91+
async fn keygen<S: Scheme>(config: &KeyGenConfig) -> Result<()> {
92+
let address = Address::precheck(&config.address)?;
93+
let pair = Pair::<S>::generate(address)?;
94+
let store = FileStore::new_checked(&config.folder, &config.id)?;
95+
store.save_key_pair(&pair)?;
96+
97+
Ok(())
98+
}

Diff for: src/core/beacon.rs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub const DEFAULT_BEACON_ID: &str = "default";

Diff for: src/core/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod beacon;

Diff for: src/key/keys.rs

+7-32
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1-
use super::Hash;
21
use super::Scheme;
32
use crate::net::utils::Address;
43

5-
use energon::backends::error::PointError;
6-
use energon::drand::SchemeError;
4+
use anyhow::Result;
75
use energon::kyber::poly::PriShare;
86
use energon::points::KeyPoint;
97
use energon::points::SigPoint;
108
use energon::traits::Affine;
119
use energon::traits::ScalarField;
12-
use sha2::Digest;
1310

1411
/// Pair is a wrapper around a random scalar and the corresponding public key
1512
#[derive(Debug, PartialEq)]
@@ -24,28 +21,17 @@ impl<S: Scheme> Pair<S> {
2421
}
2522

2623
/// Returns a freshly created private / public key pair.
27-
pub fn generate(address: Address) -> Result<Self, SchemeError> {
24+
pub fn generate(address: Address) -> Result<Self> {
2825
let private = S::Scalar::random();
2926
let key = S::sk_to_pk(&private);
3027
let mut msg = S::ID.as_bytes().to_vec();
3128
msg.extend_from_slice(key.hash()?.as_slice());
32-
3329
let signature = S::bls_sign(&msg, &private)?;
3430
let public = Identity::new(address, key, signature);
3531

3632
Ok(Self::new(private, public))
3733
}
3834

39-
/// Signs the public key with the key pair
40-
pub fn self_sign(&mut self) -> Result<(), SchemeError> {
41-
let mut msg = S::ID.as_bytes().to_vec();
42-
let pk_hash = self.public_identity().hash()?;
43-
msg.extend_from_slice(&pk_hash);
44-
self.public.signature = S::bls_sign(&msg, self.private_key())?;
45-
46-
Ok(())
47-
}
48-
4935
pub fn private_key(&self) -> &S::Scalar {
5036
&self.private
5137
}
@@ -89,8 +75,10 @@ impl<S: Scheme> Identity<S> {
8975
/// Hash returns the hash of the public key without signing the signature. The hash
9076
/// is the input to the signature Scheme. It does *not* hash the address field as
9177
/// this may need to change while the node keeps the same key.
92-
pub fn hash(&self) -> Result<[u8; 32], PointError> {
93-
self.key.hash()
78+
pub fn hash(&self) -> Result<[u8; 32]> {
79+
let hash = self.key.hash()?;
80+
81+
Ok(hash)
9482
}
9583

9684
pub fn is_valid_signature(&self) -> bool {
@@ -99,23 +87,10 @@ impl<S: Scheme> Identity<S> {
9987
msg.extend_from_slice(hash.as_slice());
10088
return S::bls_verify(&self.key, &self.signature, &msg).is_ok();
10189
}
102-
10390
false
10491
}
10592
}
10693

107-
impl<S: Scheme> Hash for Identity<S> {
108-
type Hasher = crev_common::Blake2b256;
109-
110-
fn hash(&self) -> Result<[u8; 32], PointError> {
111-
let mut h = Self::Hasher::new();
112-
let msg = self.key().serialize()?;
113-
h.update(msg);
114-
115-
Ok(h.finalize().into())
116-
}
117-
}
118-
11994
// DistPublic represents the distributed public key generated during a DKG. This
12095
// is the information that can be safely exported to end users verifying a
12196
// drand signature. It is the list of all commitments of the coefficients of the
@@ -130,7 +105,7 @@ impl<S: Scheme> DistPublic<S> {
130105
Self { commits }
131106
}
132107

133-
pub fn from_bytes(bytes: &[Vec<u8>]) -> Result<Self, PointError> {
108+
pub fn from_bytes(bytes: &[Vec<u8>]) -> Result<Self> {
134109
let mut commits = Vec::with_capacity(bytes.len());
135110

136111
for commit in bytes.iter() {

Diff for: src/key/mod.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@ pub use energon::drand::traits::DrandScheme as Scheme;
99
pub use energon::points::KeyPoint;
1010
pub use energon::points::SigPoint;
1111

12-
use energon::backends::error::PointError;
13-
1412
pub trait Hash {
1513
type Hasher;
1614

17-
fn hash(&self) -> Result<[u8; 32], PointError>;
15+
fn hash(&self) -> anyhow::Result<[u8; 32]>;
1816
}

Diff for: src/key/store.rs

+11-16
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,16 @@ const PRIVATE_PERM: u32 = 0o600;
3131
const PUBLIC_PERM: u32 = 0o664;
3232

3333
#[derive(thiserror::Error, Debug)]
34-
#[error("file_store: ")]
34+
#[error("file_store: {0}")]
3535
pub enum FileStoreError {
3636
#[error(transparent)]
3737
IO(#[from] std::io::Error),
3838
#[error("file already exists in {0}")]
3939
FileAlreadyExists(PathBuf),
4040
#[error("file is not found at {0}")]
4141
FileNotFound(PathBuf),
42-
#[error("toml error, this is not expected")]
42+
// Normally should not be possible
43+
#[error("toml error")]
4344
TomlError,
4445
#[error("key materials scheme is invalid")]
4546
InvalidScheme,
@@ -132,9 +133,7 @@ impl FileStore {
132133
}
133134

134135
pub fn save_key_pair<S: Scheme>(&self, pair: &Pair<S>) -> Result<(), FileStoreError> {
135-
let pair_toml = pair
136-
.toml_encode()
137-
.ok_or_else(|| FileStoreError::TomlError)?;
136+
let pair_toml = pair.toml_encode().ok_or(FileStoreError::TomlError)?;
138137

139138
// save private
140139
let mut f = File::create(self.private_id_file())?;
@@ -156,9 +155,7 @@ impl FileStore {
156155
}
157156

158157
pub fn save_group<S: Scheme>(&self, group: &Group<S>) -> Result<(), FileStoreError> {
159-
let group_toml = group
160-
.toml_encode()
161-
.ok_or_else(|| FileStoreError::TomlError)?;
158+
let group_toml = group.toml_encode().ok_or(FileStoreError::TomlError)?;
162159
let mut f = File::create(self.group_file())?;
163160
f.set_permissions(Permissions::from_mode(PUBLIC_PERM))?;
164161
f.write_all(group_toml.to_string().as_bytes())?;
@@ -167,9 +164,7 @@ impl FileStore {
167164
}
168165

169166
pub fn save_share<S: Scheme>(&self, share: &Share<S>) -> Result<(), FileStoreError> {
170-
let share_toml = share
171-
.toml_encode()
172-
.ok_or_else(|| FileStoreError::TomlError)?;
167+
let share_toml = share.toml_encode().ok_or(FileStoreError::TomlError)?;
173168
let mut f = File::create(self.private_share_file())?;
174169
f.set_permissions(Permissions::from_mode(PRIVATE_PERM))?;
175170
f.write_all(share_toml.to_string().as_bytes())?;
@@ -181,15 +176,16 @@ impl FileStore {
181176
pub fn load_key_pair_toml(&self) -> Result<PairToml, FileStoreError> {
182177
let private_str = std::fs::read_to_string(self.private_id_file())?;
183178
let public_str = std::fs::read_to_string(self.public_id_file())?;
179+
let pair_toml = PairToml::parse(private_str.as_str(), public_str.as_str())
180+
.ok_or(FileStoreError::TomlError)?;
184181

185-
PairToml::parse(private_str.as_str(), public_str.as_str())
186-
.ok_or_else(|| FileStoreError::TomlError)
182+
Ok(pair_toml)
187183
}
188184

189185
pub fn load_share<S: Scheme>(&self) -> Result<Share<S>, FileStoreError> {
190186
let share_str = std::fs::read_to_string(self.private_share_file())?;
191187
Toml::toml_decode(&share_str.parse().map_err(|_| FileStoreError::TomlError)?)
192-
.ok_or_else(|| FileStoreError::TomlError)
188+
.ok_or(FileStoreError::TomlError)
193189
}
194190

195191
pub fn drand_home() -> String {
@@ -244,7 +240,6 @@ mod tests {
244240
use super::*;
245241
use crate::net::utils::Address;
246242
use energon::drand::schemes::DefaultScheme;
247-
use tempfile::tempdir;
248243

249244
// #Required permissions
250245
//
@@ -261,7 +256,7 @@ mod tests {
261256
#[test]
262257
fn check_permissions_and_data() {
263258
// Build absolute path for base folder 'testnet'
264-
let temp_dir = tempdir().unwrap();
259+
let temp_dir = tempfile::TempDir::new().unwrap();
265260
let base_path = temp_dir
266261
.path()
267262
.join("testnet")

Diff for: src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ pub mod net;
77
pub mod protobuf;
88
pub mod store;
99
pub mod transport;
10+
pub mod core;

Diff for: src/log.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
use crate::cli::CliError;
21
use std::sync::Arc;
32
use tracing::Span;
43
use tracing_subscriber::fmt::time;
54
use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
65
use tracing_subscriber::util::SubscriberInitExt;
76
use tracing_subscriber::EnvFilter;
87

9-
pub fn init_log(verbose: bool) -> Result<(), CliError> {
8+
pub fn init_log(verbose: bool) -> anyhow::Result<()> {
109
let filter = EnvFilter::builder().parse_lossy(match verbose {
1110
true => "drand=debug",
1211
false => "drand=info",
@@ -22,8 +21,9 @@ pub fn init_log(verbose: bool) -> Result<(), CliError> {
2221
tracing_subscriber::registry()
2322
.with(layer)
2423
.with(filter)
25-
.try_init()
26-
.map_err(|_| CliError::LogInitError)
24+
.try_init()?;
25+
26+
Ok(())
2727
}
2828

2929
pub struct Logger {

Diff for: src/main.rs

+3-14
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,8 @@
1515
#![warn(clippy::pedantic)]
1616
use clap::Parser;
1717
use drand::cli::CLI;
18-
use std::process::ExitCode;
1918

2019
#[tokio::main]
21-
async fn main() -> ExitCode {
22-
if let Err(err) = CLI::parse().run().await {
23-
// Errors are expected to be well-formatted in release builds.
24-
eprintln!("Error: {err}");
25-
26-
#[cfg(debug_assertions)]
27-
eprintln!("Error: {err:?}");
28-
29-
ExitCode::FAILURE
30-
} else {
31-
ExitCode::SUCCESS
32-
}
33-
}
20+
async fn main() -> anyhow::Result<()> {
21+
CLI::parse().run().await
22+
}

0 commit comments

Comments
 (0)