From 901c9e89c9f8fd029ea3c8f6408f59c7b0a0a5b8 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Thu, 11 Feb 2021 09:57:17 +1100 Subject: [PATCH] No evident added value of having separate `Seed` structs Hence, reducing complexity of the codebase. Note that the seed will be used by both nectar and the cli whereas the config mod will be different so this changes helps with the next step of having a dedicated config module for each binary. --- swap/src/bin/cli.rs | 6 +- swap/src/config.rs | 4 - swap/src/config/seed.rs | 196 ---------------------------------------- swap/src/seed.rs | 154 ++++++++++++++++++++++++++++++- 4 files changed, 154 insertions(+), 206 deletions(-) delete mode 100644 swap/src/config/seed.rs diff --git a/swap/src/bin/cli.rs b/swap/src/bin/cli.rs index 82093a9f..5def8d61 100644 --- a/swap/src/bin/cli.rs +++ b/swap/src/bin/cli.rs @@ -20,7 +20,6 @@ use structopt::StructOpt; use swap::{ bitcoin, command::{Arguments, Cancel, Command, Refund, Resume}, - config, config::{ initial_setup, query_user_for_initial_testnet_config, read_config, ConfigNotInitialized, }, @@ -35,6 +34,7 @@ use swap::{ bob::{cancel::CancelError, Builder}, SwapAmounts, }, + seed::Seed, trace::init_tracing, }; use tracing::{error, info, warn}; @@ -63,9 +63,7 @@ async fn main() -> Result<()> { ); let db_path = data_dir.join("database"); - let seed = config::Seed::from_file_or_generate(&data_dir) - .expect("Could not retrieve/initialize seed") - .into(); + let seed = Seed::from_file_or_generate(&data_dir).expect("Could not retrieve/initialize seed"); // hardcode to testnet/stagenet let bitcoin_network = bitcoin::Network::Testnet; diff --git a/swap/src/config.rs b/swap/src/config.rs index f8ad3bb4..9542880f 100644 --- a/swap/src/config.rs +++ b/swap/src/config.rs @@ -11,10 +11,6 @@ use std::{ use tracing::info; use url::Url; -pub mod seed; - -pub use seed::Seed; - const DEFAULT_BITCOIND_TESTNET_URL: &str = "http://127.0.0.1:18332"; const DEFAULT_MONERO_WALLET_RPC_TESTNET_URL: &str = "http://127.0.0.1:38083/json_rpc"; diff --git a/swap/src/config/seed.rs b/swap/src/config/seed.rs deleted file mode 100644 index ef6382f7..00000000 --- a/swap/src/config/seed.rs +++ /dev/null @@ -1,196 +0,0 @@ -use crate::{fs::ensure_directory_exists, seed}; -use pem::{encode, Pem}; -use seed::SEED_LENGTH; -use std::{ - ffi::OsStr, - fmt, - fs::{self, File}, - io::{self, Write}, - path::{Path, PathBuf}, -}; - -#[derive(Clone, Copy, PartialEq)] -pub struct Seed(seed::Seed); - -impl Seed { - pub fn random() -> Result { - Ok(Seed(seed::Seed::random()?)) - } - - pub fn from_file_or_generate(data_dir: &Path) -> Result { - let file_path_buf = data_dir.join("seed.pem"); - let file_path = Path::new(&file_path_buf); - - if file_path.exists() { - return Self::from_file(&file_path); - } - - tracing::info!("No seed file found, creating at: {}", file_path.display()); - - let random_seed = Seed::random()?; - random_seed.write_to(file_path.to_path_buf())?; - - Ok(random_seed) - } - - fn from_file(seed_file: D) -> Result - where - D: AsRef, - { - let file = Path::new(&seed_file); - let contents = fs::read_to_string(file)?; - let pem = pem::parse(contents)?; - - tracing::info!("Read in seed from file: {}", file.display()); - - Self::from_pem(pem) - } - - fn from_pem(pem: pem::Pem) -> Result { - if pem.contents.len() != SEED_LENGTH { - Err(Error::IncorrectLength(pem.contents.len())) - } else { - let mut array = [0; SEED_LENGTH]; - for (i, b) in pem.contents.iter().enumerate() { - array[i] = *b; - } - - Ok(Self::from(array)) - } - } - - fn write_to(&self, seed_file: PathBuf) -> Result<(), Error> { - ensure_directory_exists(&seed_file)?; - - let data = (self.0).bytes(); - let pem = Pem { - tag: String::from("SEED"), - contents: data.to_vec(), - }; - - let pem_string = encode(&pem); - - let mut file = File::create(seed_file)?; - file.write_all(pem_string.as_bytes())?; - - Ok(()) - } -} - -impl fmt::Debug for Seed { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Seed([*****])") - } -} - -impl fmt::Display for Seed { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -impl From<[u8; SEED_LENGTH]> for Seed { - fn from(bytes: [u8; 32]) -> Self { - Seed(seed::Seed::from(bytes)) - } -} - -impl From for seed::Seed { - fn from(seed: Seed) -> Self { - seed.0 - } -} - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("Seed generation: ")] - SeedGeneration(#[from] crate::seed::Error), - #[error("io: ")] - Io(#[from] io::Error), - #[error("PEM parse: ")] - PemParse(#[from] pem::PemError), - #[error("expected 32 bytes of base64 encode, got {0} bytes")] - IncorrectLength(usize), - #[error("RNG: ")] - Rand(#[from] rand::Error), - #[error("no default path")] - NoDefaultPath, -} - -#[cfg(test)] -mod tests { - use super::*; - use std::env::temp_dir; - - #[test] - fn seed_byte_string_must_be_32_bytes_long() { - let _seed = Seed::from(*b"this string is exactly 32 bytes!"); - } - - #[test] - fn seed_from_pem_works() { - let payload: &str = "syl9wSYaruvgxg9P5Q1qkZaq5YkM6GvXkxe+VYrL/XM="; - - // 32 bytes base64 encoded. - let pem_string: &str = "-----BEGIN SEED----- -syl9wSYaruvgxg9P5Q1qkZaq5YkM6GvXkxe+VYrL/XM= ------END SEED----- -"; - - let want = base64::decode(payload).unwrap(); - let pem = pem::parse(pem_string).unwrap(); - let got = Seed::from_pem(pem).unwrap(); - - assert_eq!((got.0).bytes(), *want); - } - - #[test] - fn seed_from_pem_fails_for_short_seed() { - let short = "-----BEGIN SEED----- -VnZUNFZ4dlY= ------END SEED----- -"; - let pem = pem::parse(short).unwrap(); - match Seed::from_pem(pem) { - Ok(_) => panic!("should fail for short payload"), - Err(e) => { - match e { - Error::IncorrectLength(_) => {} // pass - _ => panic!("should fail with IncorrectLength error"), - } - } - } - } - - #[test] - #[should_panic] - fn seed_from_pem_fails_for_long_seed() { - let long = "-----BEGIN SEED----- -mbKANv2qKGmNVg1qtquj6Hx1pFPelpqOfE2JaJJAMEg1FlFhNRNlFlE= -mbKANv2qKGmNVg1qtquj6Hx1pFPelpqOfE2JaJJAMEg1FlFhNRNlFlE= ------END SEED----- -"; - let pem = pem::parse(long).unwrap(); - match Seed::from_pem(pem) { - Ok(_) => panic!("should fail for long payload"), - Err(e) => { - match e { - Error::IncorrectLength(_) => {} // pass - _ => panic!("should fail with IncorrectLength error"), - } - } - } - } - - #[test] - fn round_trip_through_file_write_read() { - let tmpfile = temp_dir().join("seed.pem"); - - let seed = Seed::random().unwrap(); - seed.write_to(tmpfile.clone()) - .expect("Write seed to temp file"); - - let rinsed = Seed::from_file(tmpfile).expect("Read from temp file"); - assert_eq!(seed.0, rinsed.0); - } -} diff --git a/swap/src/seed.rs b/swap/src/seed.rs index 382f64da..2a0b3836 100644 --- a/swap/src/seed.rs +++ b/swap/src/seed.rs @@ -1,6 +1,14 @@ +use crate::fs::ensure_directory_exists; use ::bitcoin::secp256k1::{self, constants::SECRET_KEY_SIZE, SecretKey}; +use pem::{encode, Pem}; use rand::prelude::*; -use std::fmt; +use std::{ + ffi::OsStr, + fmt, + fs::{self, File}, + io::{self, Write}, + path::{Path, PathBuf}, +}; pub const SEED_LENGTH: usize = 32; @@ -21,6 +29,65 @@ impl Seed { pub fn bytes(&self) -> [u8; SEED_LENGTH] { self.0 } + + pub fn from_file_or_generate(data_dir: &Path) -> Result { + let file_path_buf = data_dir.join("seed.pem"); + let file_path = Path::new(&file_path_buf); + + if file_path.exists() { + return Self::from_file(&file_path); + } + + tracing::info!("No seed file found, creating at: {}", file_path.display()); + + let random_seed = Seed::random()?; + random_seed.write_to(file_path.to_path_buf())?; + + Ok(random_seed) + } + + fn from_file(seed_file: D) -> Result + where + D: AsRef, + { + let file = Path::new(&seed_file); + let contents = fs::read_to_string(file)?; + let pem = pem::parse(contents)?; + + tracing::info!("Read in seed from file: {}", file.display()); + + Self::from_pem(pem) + } + + fn from_pem(pem: pem::Pem) -> Result { + if pem.contents.len() != SEED_LENGTH { + Err(Error::IncorrectLength(pem.contents.len())) + } else { + let mut array = [0; SEED_LENGTH]; + for (i, b) in pem.contents.iter().enumerate() { + array[i] = *b; + } + + Ok(Self::from(array)) + } + } + + fn write_to(&self, seed_file: PathBuf) -> Result<(), Error> { + ensure_directory_exists(&seed_file)?; + + let data = self.bytes(); + let pem = Pem { + tag: String::from("SEED"), + contents: data.to_vec(), + }; + + let pem_string = encode(&pem); + + let mut file = File::create(seed_file)?; + file.write_all(pem_string.as_bytes())?; + + Ok(()) + } } impl fmt::Debug for Seed { @@ -41,18 +108,101 @@ impl From<[u8; SEED_LENGTH]> for Seed { } } -#[derive(Debug, Copy, Clone, thiserror::Error)] +#[derive(Debug, thiserror::Error)] pub enum Error { #[error("Secp256k1: ")] Secp256k1(#[from] secp256k1::Error), + #[error("io: ")] + Io(#[from] io::Error), + #[error("PEM parse: ")] + PemParse(#[from] pem::PemError), + #[error("expected 32 bytes of base64 encode, got {0} bytes")] + IncorrectLength(usize), + #[error("RNG: ")] + Rand(#[from] rand::Error), + #[error("no default path")] + NoDefaultPath, } #[cfg(test)] mod tests { use super::*; + use std::env::temp_dir; #[test] fn generate_random_seed() { let _ = Seed::random().unwrap(); } + + #[test] + fn seed_byte_string_must_be_32_bytes_long() { + let _seed = Seed::from(*b"this string is exactly 32 bytes!"); + } + + #[test] + fn seed_from_pem_works() { + let payload: &str = "syl9wSYaruvgxg9P5Q1qkZaq5YkM6GvXkxe+VYrL/XM="; + + // 32 bytes base64 encoded. + let pem_string: &str = "-----BEGIN SEED----- +syl9wSYaruvgxg9P5Q1qkZaq5YkM6GvXkxe+VYrL/XM= +-----END SEED----- +"; + + let want = base64::decode(payload).unwrap(); + let pem = pem::parse(pem_string).unwrap(); + let got = Seed::from_pem(pem).unwrap(); + + assert_eq!(got.bytes(), *want); + } + + #[test] + fn seed_from_pem_fails_for_short_seed() { + let short = "-----BEGIN SEED----- +VnZUNFZ4dlY= +-----END SEED----- +"; + let pem = pem::parse(short).unwrap(); + match Seed::from_pem(pem) { + Ok(_) => panic!("should fail for short payload"), + Err(e) => { + match e { + Error::IncorrectLength(_) => {} // pass + _ => panic!("should fail with IncorrectLength error"), + } + } + } + } + + #[test] + #[should_panic] + fn seed_from_pem_fails_for_long_seed() { + let long = "-----BEGIN SEED----- +mbKANv2qKGmNVg1qtquj6Hx1pFPelpqOfE2JaJJAMEg1FlFhNRNlFlE= +mbKANv2qKGmNVg1qtquj6Hx1pFPelpqOfE2JaJJAMEg1FlFhNRNlFlE= +-----END SEED----- +"; + let pem = pem::parse(long).unwrap(); + match Seed::from_pem(pem) { + Ok(_) => panic!("should fail for long payload"), + Err(e) => { + match e { + Error::IncorrectLength(_) => {} // pass + _ => panic!("should fail with IncorrectLength error"), + } + } + } + } + + #[test] + fn round_trip_through_file_write_read() { + let tmpfile = temp_dir().join("seed.pem"); + + let seed = Seed::random().unwrap(); + seed.write_to(tmpfile.clone()) + .expect("Write seed to temp file"); + + let rinsed = Seed::from_file(tmpfile).expect("Read from temp file"); + assert_eq!(seed.0, rinsed.0); + } }