mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-11-17 15:26:14 +00:00
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:
parent
7d392c3086
commit
901c9e89c9
@ -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;
|
||||||
|
@ -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";
|
||||||
|
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
154
swap/src/seed.rs
154
swap/src/seed.rs
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user