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.
This commit is contained in:
Franck Royer 2021-02-11 09:57:17 +11:00
parent 7d392c3086
commit 901c9e89c9
No known key found for this signature in database
GPG Key ID: A82ED75A8DFC50A4
4 changed files with 154 additions and 206 deletions

View File

@ -20,7 +20,6 @@ use structopt::StructOpt;
use swap::{ use swap::{
bitcoin, bitcoin,
command::{Arguments, Cancel, Command, Refund, Resume}, command::{Arguments, Cancel, Command, Refund, Resume},
config,
config::{ config::{
initial_setup, query_user_for_initial_testnet_config, read_config, ConfigNotInitialized, initial_setup, query_user_for_initial_testnet_config, read_config, ConfigNotInitialized,
}, },
@ -35,6 +34,7 @@ use swap::{
bob::{cancel::CancelError, Builder}, bob::{cancel::CancelError, Builder},
SwapAmounts, SwapAmounts,
}, },
seed::Seed,
trace::init_tracing, trace::init_tracing,
}; };
use tracing::{error, info, warn}; use tracing::{error, info, warn};
@ -63,9 +63,7 @@ async fn main() -> Result<()> {
); );
let db_path = data_dir.join("database"); let db_path = data_dir.join("database");
let seed = config::Seed::from_file_or_generate(&data_dir) let seed = Seed::from_file_or_generate(&data_dir).expect("Could not retrieve/initialize seed");
.expect("Could not retrieve/initialize seed")
.into();
// hardcode to testnet/stagenet // hardcode to testnet/stagenet
let bitcoin_network = bitcoin::Network::Testnet; let bitcoin_network = bitcoin::Network::Testnet;

View File

@ -11,10 +11,6 @@ use std::{
use tracing::info; use tracing::info;
use url::Url; use url::Url;
pub mod seed;
pub use seed::Seed;
const DEFAULT_BITCOIND_TESTNET_URL: &str = "http://127.0.0.1:18332"; 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"; const DEFAULT_MONERO_WALLET_RPC_TESTNET_URL: &str = "http://127.0.0.1:38083/json_rpc";

View File

@ -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<Self, Error> {
Ok(Seed(seed::Seed::random()?))
}
pub fn from_file_or_generate(data_dir: &Path) -> Result<Self, Error> {
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<D>(seed_file: D) -> Result<Self, Error>
where
D: AsRef<OsStr>,
{
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<Self, Error> {
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<Seed> 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);
}
}

View File

@ -1,6 +1,14 @@
use crate::fs::ensure_directory_exists;
use ::bitcoin::secp256k1::{self, constants::SECRET_KEY_SIZE, SecretKey}; use ::bitcoin::secp256k1::{self, constants::SECRET_KEY_SIZE, SecretKey};
use pem::{encode, Pem};
use rand::prelude::*; 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; pub const SEED_LENGTH: usize = 32;
@ -21,6 +29,65 @@ impl Seed {
pub fn bytes(&self) -> [u8; SEED_LENGTH] { pub fn bytes(&self) -> [u8; SEED_LENGTH] {
self.0 self.0
} }
pub fn from_file_or_generate(data_dir: &Path) -> Result<Self, Error> {
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<D>(seed_file: D) -> Result<Self, Error>
where
D: AsRef<OsStr>,
{
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<Self, Error> {
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 { 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 { pub enum Error {
#[error("Secp256k1: ")] #[error("Secp256k1: ")]
Secp256k1(#[from] secp256k1::Error), 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use std::env::temp_dir;
#[test] #[test]
fn generate_random_seed() { fn generate_random_seed() {
let _ = Seed::random().unwrap(); 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);
}
} }