diff --git a/code/cli/Cargo.toml b/code/cli/Cargo.toml index 6458b830d..2850ebd3e 100644 --- a/code/cli/Cargo.toml +++ b/code/cli/Cargo.toml @@ -14,7 +14,6 @@ malachite-actors.workspace = true malachite-node.workspace = true malachite-test.workspace = true -base64 = { workspace = true } clap = { workspace = true, features = ["derive", "env"] } color-eyre = { workspace = true } directories = { workspace = true } diff --git a/code/cli/src/args.rs b/code/cli/src/args.rs index 2cd77a98d..e9bf4ca9c 100644 --- a/code/cli/src/args.rs +++ b/code/cli/src/args.rs @@ -1,16 +1,13 @@ //! Node command-line interface configuration //! -//! The node CLI reads configuration from the configuration file provided with the -//! `--config` parameter. Some configuration parameters can be overridden on the command-line. +//! The node CLI reads configuration from the configuration files found in the directory +//! provided with the `--home` global parameter. //! //! The command-line parameters are stored in the `Args` structure. //! `clap` parses the command-line parameters into this structure. -//! use std::path::{Path, PathBuf}; -use base64::prelude::BASE64_STANDARD; -use base64::Engine; use clap::{Parser, Subcommand}; use color_eyre::eyre::{eyre, Context, Result}; use directories::BaseDirs; @@ -20,6 +17,7 @@ use malachite_node::config::Config; use malachite_test::{PrivateKey, ValidatorSet}; use crate::logging::DebugSection; +use crate::priv_key::PrivValidatorKey; const APP_FOLDER: &str = ".malachite"; const CONFIG_FILE: &str = "config.toml"; @@ -29,36 +27,13 @@ const PRIV_VALIDATOR_KEY_FILE: &str = "priv_validator_key.json"; #[derive(Parser, Clone, Debug, Default)] #[command(version, about, long_about = None)] pub struct Args { - /// Config file path - #[arg(long, value_name = "HOME_DIR")] + /// Home directory for Malachite (default: `~/.malachite`) + #[arg(long, global = true, value_name = "HOME_DIR")] pub home: Option, - /// Config file path - #[arg(short, long, value_name = "CONFIG_FILE")] - pub config: Option, - - /// Genesis file path - #[arg(short, long, value_name = "GENESIS_FILE")] - pub genesis: Option, - - /// Base64-encoded private key #[clap( long, - default_value = "", - hide_default_value = true, - value_name = "BASE64_STRING", - env = "PRIVATE_KEY", - value_parser = | s: & str | BASE64_STANDARD.decode(s) - )] - pub private_key: std::vec::Vec, // Keep the fully qualified path for Vec or else clap will not be able to parse it: https://github.com/clap-rs/clap/issues/4481. - - /// Validator index only for the init command - #[clap(short, long, value_name = "INDEX", env = "INDEX")] - pub index: Option, - - #[clap( - short, - long = "debug", + global = true, help = "Enable debug output for the given comma-separated sections", value_enum, value_delimiter = ',' @@ -71,12 +46,26 @@ pub struct Args { #[derive(Subcommand, Clone, Debug, Default, PartialEq)] pub enum Commands { - /// Initialize configuration - /// - Init, /// Start node #[default] Start, + + /// Initialize configuration + Init, + + /// Generate testnet configuration + Testnet(TestnetArgs), +} + +#[derive(Parser, Debug, Clone, PartialEq)] +pub struct TestnetArgs { + /// Number of validator nodes in the testnet + #[clap(short, long)] + pub nodes: usize, + + /// Generate deterministic private keys for reproducibility + #[clap(short, long)] + pub deterministic: bool, } impl Args { @@ -105,19 +94,13 @@ impl Args { /// get_config_file_path returns the configuration file path based on the command-ine arguments /// and the configuration folder. pub fn get_config_file_path(&self) -> Result { - Ok(match &self.config { - Some(path) => path.clone(), - None => self.get_config_dir()?.join(CONFIG_FILE), - }) + Ok(self.get_config_dir()?.join(CONFIG_FILE)) } /// get_genesis_file_path returns the genesis file path based on the command-line arguments and /// the configuration folder. pub fn get_genesis_file_path(&self) -> Result { - Ok(match &self.genesis { - Some(path) => path.clone(), - None => self.get_config_dir()?.join(GENESIS_FILE), - }) + Ok(self.get_config_dir()?.join(GENESIS_FILE)) } /// get_priv_validator_key_file_path returns the private validator key file path based on the @@ -143,18 +126,10 @@ impl Args { /// load_private_key returns the private key either from the command-line parameter or /// from the priv_validator_key.json file. pub fn load_private_key(&self) -> Result { - if self.private_key.is_empty() - || self.private_key == vec![0u8; 32] - || self.private_key.len() < 32 - { - let priv_key_file = self.get_priv_validator_key_file_path()?; - info!("Loading private key from {:?}", priv_key_file.display()); - load_json_file(&priv_key_file) - } else { - let mut key: [u8; 32] = [0; 32]; - key.copy_from_slice(&self.private_key); - Ok(PrivateKey::from(key)) - } + let priv_key_file = self.get_priv_validator_key_file_path()?; + info!("Loading private key from {:?}", priv_key_file.display()); + let priv_validator_key: PrivValidatorKey = load_json_file(&priv_key_file)?; + Ok(priv_validator_key.private_key) } } @@ -193,22 +168,6 @@ mod tests { let args = Args::parse_from(["test", "start"]); assert_eq!(args.debug, vec![]); assert_eq!(args.command, Commands::Start); - - let args = Args::parse_from([ - "test", - "--config", - "myconfig.toml", - "--genesis", - "mygenesis.json", - "--private-key", - "c2VjcmV0", - "init", - ]); - assert_eq!(args.config, Some(PathBuf::from("myconfig.toml"))); - assert_eq!(args.genesis, Some(PathBuf::from("mygenesis.json"))); - assert_eq!(args.private_key, b"secret"); - assert!(args.get_home_dir().is_ok()); - assert!(args.get_config_dir().is_ok()); } #[test] @@ -229,41 +188,4 @@ mod tests { writeln!(file, "{{}}").unwrap(); assert!(load_json_file::(&PathBuf::from(tmpfile.path())).is_ok()); } - - #[test] - fn args_load_config() { - let args = Args::parse_from(["test", "--config", "../config.toml", "start"]); - let config = args.load_config().unwrap(); - assert_eq!(config.moniker, "malachite"); - } - - #[test] - fn args_load_genesis() { - let args = Args::parse_from(["test", "--genesis", "../genesis.json", "start"]); - assert!(args.load_genesis().is_err()); - } - - #[test] - fn args_private_key() { - let args = Args::parse_from(["test", "start"]); - if !args.get_priv_validator_key_file_path().unwrap().exists() { - assert!(args.load_private_key().is_err()); - assert!(args.private_key.is_empty()); - } - - let args = Args::parse_from(["test", "--private-key", "c2VjcmV0", "start"]); - if !args.get_priv_validator_key_file_path().unwrap().exists() { - assert!(args.load_private_key().is_err()); - } - - let args = Args::parse_from([ - "test", - "--private-key", - "c2VjcmV0c2VjcmV0c2VjcmV0c2VjcmV0c2VjcmV0MDA=", - "start", - ]); - let pk = args.load_private_key().unwrap(); - - assert_eq!(pk.inner().as_bytes(), b"secretsecretsecretsecretsecret00"); - } } diff --git a/code/cli/src/cmd/init.rs b/code/cli/src/cmd/init.rs index 15d40cf40..1a362e2ef 100644 --- a/code/cli/src/cmd/init.rs +++ b/code/cli/src/cmd/init.rs @@ -7,49 +7,49 @@ use color_eyre::eyre::{eyre, Context, Result}; use tracing::{info, warn}; use malachite_node::config::Config; -use malachite_test::PrivateKey; use malachite_test::ValidatorSet as Genesis; -use crate::example::{generate_config, generate_genesis, generate_private_key}; +use crate::cmd::testnet::{generate_config, generate_genesis, generate_private_keys}; +use crate::priv_key::PrivValidatorKey; /// Execute the init command -pub fn run( - config_file: &Path, - genesis_file: &Path, - priv_validator_key_file: &Path, - index: usize, -) -> Result<()> { +pub fn run(config_file: &Path, genesis_file: &Path, priv_validator_key_file: &Path) -> Result<()> { // Save default configuration if config_file.exists() { warn!( - "Configuration file already exists at {:?}, skipping.", + "Configuration file already exists at {:?}, skipping", config_file.display() ) } else { - info!("Saving configuration to {:?}.", config_file); - save_config(config_file, &generate_config(index))?; + info!("Saving configuration to {:?}", config_file); + save_config(config_file, &generate_config(0, 1))?; } // Save default genesis if genesis_file.exists() { warn!( - "Genesis file already exists at {:?}, skipping.", + "Genesis file already exists at {:?}, skipping", genesis_file.display() ) } else { + let private_keys = generate_private_keys(1, true); + let public_keys = private_keys.iter().map(|pk| pk.public_key()).collect(); + let genesis = generate_genesis(public_keys, true); info!("Saving test genesis to {:?}.", genesis_file); - save_genesis(genesis_file, &generate_genesis())?; + save_genesis(genesis_file, &genesis)?; } // Save default priv_validator_key if priv_validator_key_file.exists() { warn!( - "Private key file already exists at {:?}, skipping.", + "Private key file already exists at {:?}, skipping", priv_validator_key_file.display() ) } else { - info!("Saving private key to {:?}.", priv_validator_key_file); - save_priv_validator_key(priv_validator_key_file, &generate_private_key(index))?; + info!("Saving private key to {:?}", priv_validator_key_file); + let private_keys = generate_private_keys(1, false); + let priv_validator_key = PrivValidatorKey::from(private_keys[0].clone()); + save_priv_validator_key(priv_validator_key_file, &priv_validator_key)?; } Ok(()) @@ -68,11 +68,11 @@ pub fn save_genesis(genesis_file: &Path, genesis: &Genesis) -> Result<()> { /// Save private_key validator key to file pub fn save_priv_validator_key( priv_validator_key_file: &Path, - private_key: &PrivateKey, + priv_validator_key: &PrivValidatorKey, ) -> Result<()> { save( priv_validator_key_file, - &serde_json::to_string_pretty(private_key)?, + &serde_json::to_string_pretty(priv_validator_key)?, ) } diff --git a/code/cli/src/cmd/mod.rs b/code/cli/src/cmd/mod.rs index 56c2f9754..8bfe14741 100644 --- a/code/cli/src/cmd/mod.rs +++ b/code/cli/src/cmd/mod.rs @@ -1,2 +1,3 @@ pub mod init; pub mod start; +pub mod testnet; diff --git a/code/cli/src/cmd/testnet.rs b/code/cli/src/cmd/testnet.rs new file mode 100644 index 000000000..71d7f2638 --- /dev/null +++ b/code/cli/src/cmd/testnet.rs @@ -0,0 +1,135 @@ +//! Testnet command + +use std::path::Path; + +use color_eyre::eyre::Result; +use rand::prelude::StdRng; +use rand::rngs::OsRng; +use rand::{Rng, SeedableRng}; +use tracing::info; + +use malachite_node::config::{Config, ConsensusConfig, MempoolConfig, P2pConfig, TimeoutConfig}; +use malachite_test::ValidatorSet as Genesis; +use malachite_test::{PrivateKey, PublicKey, Validator}; + +use crate::args::Args; +use crate::cmd::init::{save_config, save_genesis, save_priv_validator_key}; +use crate::priv_key::PrivValidatorKey; + +const MIN_VOTING_POWER: u64 = 8; +const MAX_VOTING_POWER: u64 = 15; + +/// Execute the testnet command +pub fn run(home_dir: &Path, nodes: usize, deterministic: bool) -> Result<()> { + let private_keys = generate_private_keys(nodes, deterministic); + let public_keys = private_keys.iter().map(|pk| pk.public_key()).collect(); + let genesis = generate_genesis(public_keys, deterministic); + + for (i, private_key) in private_keys.iter().enumerate().take(nodes) { + // Use home directory `home_dir/` + let node_home_dir = home_dir.join(i.to_string()); + + info!( + "Generating configuration for node {i} at `{}`...", + node_home_dir.display() + ); + + // Set the destination folder + let args = Args { + home: Some(node_home_dir), + ..Args::default() + }; + + // Save private key + let priv_validator_key = PrivValidatorKey::from(private_key.clone()); + save_priv_validator_key( + &args.get_priv_validator_key_file_path()?, + &priv_validator_key, + )?; + + // Save genesis + save_genesis(&args.get_genesis_file_path()?, &genesis)?; + + // Save config + save_config(&args.get_config_file_path()?, &generate_config(i, nodes))?; + } + Ok(()) +} + +/// Generate private keys. Random or deterministic for different use-cases. +pub fn generate_private_keys(size: usize, deterministic: bool) -> Vec { + if deterministic { + let mut rng = StdRng::seed_from_u64(0x42); + (0..size).map(|_| PrivateKey::generate(&mut rng)).collect() + } else { + (0..size).map(|_| PrivateKey::generate(OsRng)).collect() + } +} + +/// Generate a Genesis file from the public keys and voting power. +/// Voting power can be random or deterministically pseudo-random. +pub fn generate_genesis(pks: Vec, deterministic: bool) -> Genesis { + let size = pks.len(); + let voting_powers: Vec = if deterministic { + let mut rng = StdRng::seed_from_u64(0x42); + (0..size) + .map(|_| rng.gen_range(MIN_VOTING_POWER..=MAX_VOTING_POWER)) + .collect() + } else { + (0..size) + .map(|_| OsRng.gen_range(MIN_VOTING_POWER..=MAX_VOTING_POWER)) + .collect() + }; + + let mut validators = Vec::with_capacity(size); + + for i in 0..size { + validators.push(Validator::new(pks[i], voting_powers[i])); + } + + Genesis { validators } +} + +const CONSENSUS_BASE_PORT: usize = 27000; +const MEMPOOL_BASE_PORT: usize = 28000; + +/// Generate configuration for node "index" out of "total" number of nodes. +pub fn generate_config(index: usize, total: usize) -> Config { + let consensus_port = CONSENSUS_BASE_PORT + index; + let mempool_port = MEMPOOL_BASE_PORT + index; + + Config { + moniker: format!("test-{}", index), + consensus: ConsensusConfig { + timeouts: TimeoutConfig::default(), + p2p: P2pConfig { + listen_addr: format!("/ip4/127.0.0.1/udp/{consensus_port}/quic-v1") + .parse() + .unwrap(), + persistent_peers: (0..total) + .filter(|j| *j != index) + .map(|j| { + format!("/ip4/127.0.0.1/udp/{}/quic-v1", CONSENSUS_BASE_PORT + j) + .parse() + .unwrap() + }) + .collect(), + }, + }, + mempool: MempoolConfig { + p2p: P2pConfig { + listen_addr: format!("/ip4/127.0.0.1/udp/{mempool_port}/quic-v1") + .parse() + .unwrap(), + persistent_peers: (0..total) + .filter(|j| *j != index) + .map(|j| { + format!("/ip4/127.0.0.1/udp/{}/quic-v1", MEMPOOL_BASE_PORT + j) + .parse() + .unwrap() + }) + .collect(), + }, + }, + } +} diff --git a/code/cli/src/example.rs b/code/cli/src/example.rs deleted file mode 100644 index e22722d34..000000000 --- a/code/cli/src/example.rs +++ /dev/null @@ -1,76 +0,0 @@ -/// Example configurations for testing with indexed validators -use malachite_node::config::{Config, ConsensusConfig, MempoolConfig, P2pConfig, TimeoutConfig}; -use malachite_test::ValidatorSet as Genesis; -use malachite_test::{PrivateKey, Validator}; -use rand::prelude::StdRng; -use rand::SeedableRng; - -const CONSENSUS_BASE_PORT: usize = 27000; -const MEMPOOL_BASE_PORT: usize = 28000; - -/// Generate example configuration -pub fn generate_config(index: usize) -> Config { - let consensus_port = CONSENSUS_BASE_PORT + index; - let mempool_port = MEMPOOL_BASE_PORT + index; - - Config { - moniker: format!("test-{}", index), - consensus: ConsensusConfig { - timeouts: TimeoutConfig::default(), - p2p: P2pConfig { - listen_addr: format!("/ip4/127.0.0.1/udp/{consensus_port}/quic-v1") - .parse() - .unwrap(), - persistent_peers: (0..3) - .filter(|j| *j != index) - .map(|j| { - format!("/ip4/127.0.0.1/udp/{}/quic-v1", CONSENSUS_BASE_PORT + j) - .parse() - .unwrap() - }) - .collect(), - }, - }, - mempool: MempoolConfig { - p2p: P2pConfig { - listen_addr: format!("/ip4/127.0.0.1/udp/{mempool_port}/quic-v1") - .parse() - .unwrap(), - persistent_peers: (0..3) - .filter(|j| *j != index) - .map(|j| { - format!("/ip4/127.0.0.1/udp/{}/quic-v1", MEMPOOL_BASE_PORT + j) - .parse() - .unwrap() - }) - .collect(), - }, - }, - } -} - -/// Generate an example genesis configuration -pub fn generate_genesis() -> Genesis { - let voting_power = vec![11, 10, 10]; - - let mut rng = StdRng::seed_from_u64(0x42); - let mut validators = Vec::with_capacity(voting_power.len()); - - for vp in voting_power { - validators.push(Validator::new( - PrivateKey::generate(&mut rng).public_key(), - vp, - )); - } - - Genesis { validators } -} - -/// Generate an example private key -pub fn generate_private_key(index: usize) -> PrivateKey { - let mut rng = StdRng::seed_from_u64(0x42); - for _ in 0..index { - let _ = PrivateKey::generate(&mut rng); - } - PrivateKey::generate(&mut rng) -} diff --git a/code/cli/src/main.rs b/code/cli/src/main.rs index 3d9cbf8c5..865409c8c 100644 --- a/code/cli/src/main.rs +++ b/code/cli/src/main.rs @@ -1,17 +1,16 @@ use color_eyre::eyre::Result; -use rand::rngs::OsRng; use tracing::debug; use malachite_node::config::Config; use malachite_test::{PrivateKey, ValidatorSet}; -use crate::args::{Args, Commands}; +use crate::args::{Args, Commands, TestnetArgs}; use crate::logging::LogLevel; mod args; mod cmd; -mod example; mod logging; +mod priv_key; #[tokio::main(flavor = "current_thread")] pub async fn main() -> Result<()> { @@ -22,30 +21,34 @@ pub async fn main() -> Result<()> { debug!("Command-line parameters: {args:?}"); match args.command { - Commands::Init => init(&args), Commands::Start => start(&args).await, + Commands::Init => init(&args), + Commands::Testnet(ref testnet_args) => testnet(&args, testnet_args), } } +async fn start(args: &Args) -> Result<()> { + let cfg: Config = args.load_config()?; + let sk: PrivateKey = args.load_private_key()?; + let vs: ValidatorSet = args.load_genesis()?; + + cmd::start::run(sk, cfg, vs).await +} + fn init(args: &Args) -> Result<()> { cmd::init::run( &args.get_config_file_path()?, &args.get_genesis_file_path()?, &args.get_priv_validator_key_file_path()?, - args.index.unwrap_or(0), ) } -async fn start(args: &Args) -> Result<()> { - let cfg: Config = args.load_config()?; - - let sk: PrivateKey = args - .load_private_key() - .unwrap_or_else(|_| PrivateKey::generate(OsRng)); - - let vs: ValidatorSet = args.load_genesis()?; - - cmd::start::run(sk, cfg, vs).await +fn testnet(args: &Args, testnet_args: &TestnetArgs) -> Result<()> { + cmd::testnet::run( + &args.get_home_dir()?, + testnet_args.nodes, + testnet_args.deterministic, + ) } #[cfg(test)] @@ -61,25 +64,61 @@ mod tests { #[test] fn running_init_creates_config_files() -> eyre::Result<()> { let tmp = tempfile::tempdir()?; + let config_dir = tmp.path().join("config"); - let config = tmp.path().join("config.toml"); - let genesis = tmp.path().join("genesis.json"); + let args = Args::parse_from(["test", "--home", tmp.path().to_str().unwrap(), "init"]); + + init(&args)?; + + let files = fs::read_dir(&config_dir)?.flatten().collect::>(); + + dbg!(&files); + + assert!(has_file(&files, &config_dir.join("config.toml"))); + assert!(has_file(&files, &config_dir.join("genesis.json"))); + assert!(has_file( + &files, + &config_dir.join("priv_validator_key.json") + )); + + Ok(()) + } + + #[test] + fn running_testnet_creates_all_configs() -> eyre::Result<()> { + let tmp = tempfile::tempdir()?; let args = Args::parse_from([ "test", - "--config", - &config.display().to_string(), - "--genesis", - &genesis.display().to_string(), - "init", + "--home", + tmp.path().to_str().unwrap(), + "testnet", + "--nodes", + "3", ]); - init(&args)?; + let Commands::Testnet(ref testnet_args) = args.command else { + panic!("not testnet command"); + }; + + testnet(&args, testnet_args)?; + + let files = fs::read_dir(&tmp)?.flatten().collect::>(); + + assert_eq!(files.len(), 3); + + assert!(has_file(&files, &tmp.path().join("0"))); + assert!(has_file(&files, &tmp.path().join("1"))); + assert!(has_file(&files, &tmp.path().join("2"))); - let files = fs::read_dir(tmp.path())?.flatten().collect::>(); + for node in 0..3 { + let node_dir = tmp.path().join(node.to_string()).join("config"); + let files = fs::read_dir(&node_dir)?.flatten().collect::>(); - assert!(has_file(&files, &config)); - assert!(has_file(&files, &genesis)); + assert!(has_file(&files, &node_dir.join("config.toml"))); + assert!(has_file(&files, &node_dir.join("genesis.json"))); + assert!(has_file(&files, &node_dir.join("priv_validator_key.json"))); + } Ok(()) } diff --git a/code/cli/src/priv_key.rs b/code/cli/src/priv_key.rs new file mode 100644 index 000000000..9115025de --- /dev/null +++ b/code/cli/src/priv_key.rs @@ -0,0 +1,23 @@ +use serde::{Deserialize, Serialize}; + +use malachite_test::{Address, PrivateKey, PublicKey}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct PrivValidatorKey { + pub address: Address, + pub public_key: PublicKey, + pub private_key: PrivateKey, +} + +impl From for PrivValidatorKey { + fn from(private_key: PrivateKey) -> Self { + let public_key = private_key.public_key(); + let address = Address::from_public_key(&public_key); + + Self { + address, + public_key, + private_key, + } + } +} diff --git a/code/test/configs/README.md b/code/test/configs/README.md new file mode 100644 index 000000000..69f17629c --- /dev/null +++ b/code/test/configs/README.md @@ -0,0 +1,5 @@ +This folder was generated by the testnet command. To recreate it, run: +``` +malachite-cli --home . testnet --nodes 3 --deterministic +``` + diff --git a/code/test/src/serialization.rs b/code/test/src/serialization.rs index 8ae387a4d..6c7a7c982 100644 --- a/code/test/src/serialization.rs +++ b/code/test/src/serialization.rs @@ -1,5 +1,5 @@ /// Serde Ed25519 VerificationKey CometBFT serializer/deserializer. -pub mod verificationkey { +pub mod verification_key { use ed25519_consensus::VerificationKey; use serde::{Deserialize, Serialize, Serializer}; @@ -31,6 +31,39 @@ pub mod verificationkey { } } +/// Serde Ed25519 SigningKey CometBFT serializer/deserializer. +pub mod signing_key { + use ed25519_consensus::SigningKey; + use serde::{Deserialize, Serialize, Serializer}; + + #[derive(Serialize, Deserialize)] + struct PrivKey { + #[serde(rename = "type")] + key_type: String, + #[serde(with = "crate::serialization::base64string")] + value: Vec, + } + + pub fn serialize(s: &SigningKey, ser: S) -> Result + where + S: Serializer, + { + PrivKey { + key_type: "tendermint/PrivKeyEd25519".to_string(), + value: s.as_bytes().to_vec(), + } + .serialize(ser) + } + + pub fn deserialize<'de, D>(de: D) -> Result + where + D: serde::Deserializer<'de>, + { + let pk = PrivKey::deserialize(de)?; + SigningKey::try_from(pk.value.as_slice()).map_err(serde::de::Error::custom) + } +} + /// Serialize/deserialize between base64-encoded String and Vec pub mod base64string { use base64::prelude::BASE64_STANDARD; diff --git a/code/test/src/signing.rs b/code/test/src/signing.rs index fb5dbe98b..605e881eb 100644 --- a/code/test/src/signing.rs +++ b/code/test/src/signing.rs @@ -36,7 +36,9 @@ impl SigningScheme for Ed25519 { #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(transparent)] -pub struct PrivateKey(ed25519_consensus::SigningKey); +pub struct PrivateKey( + #[serde(with = "crate::serialization::signing_key")] ed25519_consensus::SigningKey, +); impl PrivateKey { #[cfg_attr(coverage_nightly, coverage(off))] @@ -83,7 +85,7 @@ impl Keypair for PrivateKey { #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(transparent)] pub struct PublicKey( - #[serde(with = "crate::serialization::verificationkey")] ed25519_consensus::VerificationKey, + #[serde(with = "crate::serialization::verification_key")] ed25519_consensus::VerificationKey, ); impl PublicKey {