diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92dc79c..90b2278 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,10 +76,13 @@ jobs: - name: Smoke test ${{ matrix.os }} binary shell: bash run: | - target/debug/maker --data-dir=/tmp/maker --generate-seed testnet & - sleep 10s # Wait for maker to start + mkdir -p /tmp/maker/testnet + cp -r daemon/util/testnet_seeds/maker_seed /tmp/maker/testnet/maker_seed + target/debug/maker --data-dir=/tmp/maker testnet & + sleep 10s # Wait for maker to start\ - target/debug/taker --data-dir=/tmp/taker --generate-seed testnet & + # The maker-id is generated from the makers seed found in daemon/util/testnet_seeds/maker_seed + target/debug/taker --data-dir=/tmp/taker --generate-seed --maker-id 10d4ba2ac3f7a22da4009d813ff1bc3f404dfe2cc93a32bedf1512aa9951c95e testnet & sleep 10s # Wait for taker to start curl --fail http://localhost:8000/api/alive diff --git a/Cargo.lock b/Cargo.lock index f5040bf..02414cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -672,6 +672,7 @@ dependencies = [ "tracing-subscriber", "uuid", "vergen", + "x25519-dalek", "xtra", "xtra_productivity", ] diff --git a/daemon/Cargo.toml b/daemon/Cargo.toml index d59c2f8..af4922d 100644 --- a/daemon/Cargo.toml +++ b/daemon/Cargo.toml @@ -42,6 +42,7 @@ tokio-util = { version = "0.6", features = ["codec"] } tracing = { version = "0.1" } tracing-subscriber = { version = "0.2", default-features = false, features = ["fmt", "ansi", "env-filter", "chrono", "tracing-log", "json"] } uuid = { version = "0.8", features = ["serde", "v4"] } +x25519-dalek = { version = "1.1" } xtra = { version = "0.6", features = ["with-tokio-1"] } xtra_productivity = { path = "../xtra_productivity" } diff --git a/daemon/src/connection.rs b/daemon/src/connection.rs index e75dd5e..a0be233 100644 --- a/daemon/src/connection.rs +++ b/daemon/src/connection.rs @@ -17,12 +17,20 @@ pub struct Actor { } impl Actor { - pub async fn new(maker: SocketAddr) -> Result { + pub async fn new( + maker_addr: SocketAddr, + maker_noise_static_pk: x25519_dalek::PublicKey, + noise_static_sk: x25519_dalek::StaticSecret, + ) -> Result { let (read, write, noise) = loop { let socket = tokio::net::TcpSocket::new_v4().expect("Be able ta create a socket"); - if let Ok(mut connection) = socket.connect(maker).await { - let noise = noise::initiator_handshake(&mut connection).await?; - + if let Ok(mut connection) = socket.connect(maker_addr).await { + let noise = noise::initiator_handshake( + &mut connection, + &noise_static_sk, + &maker_noise_static_pk, + ) + .await?; let (read, write) = connection.into_split(); break (read, write, Arc::new(Mutex::new(noise))); } else { diff --git a/daemon/src/maker.rs b/daemon/src/maker.rs index 22f78b9..bd31bf2 100644 --- a/daemon/src/maker.rs +++ b/daemon/src/maker.rs @@ -198,10 +198,14 @@ async fn main() -> Result<()> { let auth_password = seed.derive_auth_password::(); + let noise_static_sk = seed.derive_noise_static_secret(); + let noise_static_pk = x25519_dalek::PublicKey::from(&noise_static_sk); + tracing::info!( - "Authentication details: username='{}' password='{}'", + "Authentication details: username='{}' password='{}', noise_public_key='{}'", MAKER_USERNAME, - auth_password + auth_password, + hex::encode(noise_static_pk.to_bytes()) ); // TODO: Actually fetch it from Olivia @@ -264,7 +268,7 @@ async fn main() -> Result<()> { monitor::Actor::new(electrum, channel, cfds) } }, - |channel0, channel1| maker_inc_connections::Actor::new(channel0, channel1), + |channel0, channel1| maker_inc_connections::Actor::new(channel0, channel1, noise_static_sk), time::Duration::hours(opts.settlement_time_interval_hours as i64), ) .await?; diff --git a/daemon/src/maker_inc_connections.rs b/daemon/src/maker_inc_connections.rs index 2ca7743..a548eb7 100644 --- a/daemon/src/maker_inc_connections.rs +++ b/daemon/src/maker_inc_connections.rs @@ -67,17 +67,20 @@ pub struct Actor { write_connections: HashMap>>, new_taker_channel: Box>, taker_msg_channel: Box>, + noise_priv_key: x25519_dalek::StaticSecret, } impl Actor { pub fn new( new_taker_channel: Box>, taker_msg_channel: Box>, + noise_priv_key: x25519_dalek::StaticSecret, ) -> Self { Self { write_connections: HashMap::new(), new_taker_channel: new_taker_channel.clone_channel(), taker_msg_channel: taker_msg_channel.clone_channel(), + noise_priv_key, } } @@ -96,14 +99,16 @@ impl Actor { async fn handle_new_connection_impl( &mut self, mut stream: TcpStream, - address: SocketAddr, + taker_address: SocketAddr, _: &mut Context, ) -> Result<()> { let taker_id = TakerId::default(); - tracing::info!("New taker {} connected on {}", taker_id, address); + tracing::info!("New taker {} connected on {}", taker_id, taker_address); - let noise = Arc::new(Mutex::new(noise::responder_handshake(&mut stream).await?)); + let noise = Arc::new(Mutex::new( + noise::responder_handshake(&mut stream, &self.noise_priv_key).await?, + )); let (read, write) = stream.into_split(); let read = FramedRead::new(read, wire::EncryptedJsonCodec::new(noise.clone())) diff --git a/daemon/src/noise.rs b/daemon/src/noise.rs index 62a8950..f64661c 100644 --- a/daemon/src/noise.rs +++ b/daemon/src/noise.rs @@ -6,15 +6,19 @@ use tokio::net::TcpStream; pub static NOISE_MAX_MSG_LEN: u32 = 65535; pub static NOISE_TAG_LEN: u32 = 16; -// TODO: investigate what these params do and whether we can use get authentication for free using -// them -static NOISE_PARAMS: &str = "Noise_XX_25519_ChaChaPoly_BLAKE2s"; +static NOISE_PARAMS: &str = "Noise_IK_25519_ChaChaPoly_BLAKE2s"; -pub async fn initiator_handshake(connection: &mut TcpStream) -> Result { +pub async fn initiator_handshake( + connection: &mut TcpStream, + local_priv_key: &x25519_dalek::StaticSecret, + remote_pub_key: &x25519_dalek::PublicKey, +) -> Result { let builder: Builder<'_> = Builder::new(NOISE_PARAMS.parse()?); - let static_key = builder.generate_keypair()?.private; - let mut noise = builder.local_private_key(&static_key).build_initiator()?; + let mut noise = builder + .local_private_key(&local_priv_key.to_bytes()) + .remote_public_key(&remote_pub_key.to_bytes()) + .build_initiator()?; let mut buf = vec![0u8; NOISE_MAX_MSG_LEN as usize]; @@ -23,9 +27,6 @@ pub async fn initiator_handshake(connection: &mut TcpStream) -> Result Result Result { +pub async fn responder_handshake( + connection: &mut TcpStream, + local_priv_key: &x25519_dalek::StaticSecret, +) -> Result { let builder: Builder<'_> = Builder::new(NOISE_PARAMS.parse()?); - let static_key = builder.generate_keypair()?.private; - let mut noise = builder.local_private_key(&static_key).build_responder()?; + let mut noise = builder + .local_private_key(&local_priv_key.to_bytes()) + .build_responder()?; let mut buf = vec![0u8; NOISE_MAX_MSG_LEN as usize]; @@ -46,8 +51,6 @@ pub async fn responder_handshake(connection: &mut TcpStream) -> Result x25519_dalek::StaticSecret { + let mut secret = [0u8; 32]; + + Hkdf::::new(None, &self.0) + .expand(b"NOISE_STATIC_SECRET", &mut secret) + .expect("okm array is of correct length"); + + x25519_dalek::StaticSecret::from(secret) + } } impl Default for Seed { diff --git a/daemon/src/taker.rs b/daemon/src/taker.rs index eb3496e..5b66125 100644 --- a/daemon/src/taker.rs +++ b/daemon/src/taker.rs @@ -31,6 +31,10 @@ struct Opts { #[clap(long, default_value = "127.0.0.1:9999")] maker: SocketAddr, + /// The public key of the maker as a 32 byte hex string. + #[clap(long, parse(try_from_str = parse_x25519_pubkey))] + maker_id: x25519_dalek::PublicKey, + /// The IP address to listen on for the HTTP API. #[clap(long, default_value = "127.0.0.1:8000")] http_address: SocketAddr, @@ -51,6 +55,12 @@ struct Opts { network: Network, } +fn parse_x25519_pubkey(s: &str) -> Result { + let mut bytes = [0u8; 32]; + hex::decode_to_slice(s, &mut bytes)?; + Ok(x25519_dalek::PublicKey::from(bytes)) +} + #[derive(Parser)] enum Network { Mainnet { @@ -153,6 +163,7 @@ async fn main() -> Result<()> { let bitcoin_network = opts.network.bitcoin_network(); let ext_priv_key = seed.derive_extended_priv_key(bitcoin_network)?; + let noise_static_sk = seed.derive_noise_static_secret(); let wallet = wallet::Actor::new( opts.network.electrum(), @@ -219,7 +230,7 @@ async fn main() -> Result<()> { let connection::Actor { send_to_maker, read_from_maker, - } = connection::Actor::new(opts.maker).await?; + } = connection::Actor::new(opts.maker, opts.maker_id, noise_static_sk).await?; let TakerActorSystem { cfd_actor_addr, diff --git a/daemon/tests/harness/mod.rs b/daemon/tests/harness/mod.rs index f98c436..5225cd3 100644 --- a/daemon/tests/harness/mod.rs +++ b/daemon/tests/harness/mod.rs @@ -5,6 +5,7 @@ use crate::schnorrsig; use daemon::maker_cfd::CfdAction; use daemon::model::cfd::{Cfd, Order}; use daemon::model::Usd; +use daemon::seed::Seed; use daemon::{connection, db, maker_cfd, maker_inc_connections, taker_cfd}; use sqlx::SqlitePool; use std::net::SocketAddr; @@ -25,7 +26,7 @@ pub async fn start_both() -> (Maker, Taker) { .unwrap(); let maker = Maker::start(oracle_pk).await; - let taker = Taker::start(oracle_pk, maker.listen_addr).await; + let taker = Taker::start(oracle_pk, maker.listen_addr, maker.noise_static_pk).await; (maker, taker) } @@ -41,6 +42,7 @@ pub struct Maker { pub inc_conn_actor_addr: xtra::Address, pub listen_addr: SocketAddr, pub mocks: mocks::Mocks, + pub noise_static_pk: x25519_dalek::PublicKey, } impl Maker { @@ -59,13 +61,20 @@ impl Maker { let settlement_time_interval_hours = time::Duration::hours(24); + let seed = Seed::default(); + + let noise_static_sk = seed.derive_noise_static_secret(); + let noise_static_pk = x25519_dalek::PublicKey::from(&noise_static_sk); + let maker = daemon::MakerActorSystem::new( db, wallet_addr, oracle_pk, |_, _| oracle, |_, _| async { Ok(monitor) }, - |channel0, channel1| maker_inc_connections::Actor::new(channel0, channel1), + |channel0, channel1| { + maker_inc_connections::Actor::new(channel0, channel1, noise_static_sk) + }, settlement_time_interval_hours, ) .await @@ -95,6 +104,7 @@ impl Maker { cfd_feed: maker.cfd_feed_receiver, inc_conn_actor_addr: maker.inc_conn_addr, listen_addr: address, + noise_static_pk, mocks, } } @@ -126,11 +136,19 @@ pub struct Taker { } impl Taker { - pub async fn start(oracle_pk: schnorrsig::PublicKey, maker_address: SocketAddr) -> Self { + pub async fn start( + oracle_pk: schnorrsig::PublicKey, + maker_address: SocketAddr, + maker_noise_pub_key: x25519_dalek::PublicKey, + ) -> Self { + let seed = Seed::default(); + + let noise_static_sk = seed.derive_noise_static_secret(); + let connection::Actor { send_to_maker, read_from_maker, - } = connection::Actor::new(maker_address) + } = connection::Actor::new(maker_address, maker_noise_pub_key, noise_static_sk) .await .expect("Connected to maker"); diff --git a/start_all.sh b/start_all.sh index 4f8926d..dc17cf4 100755 --- a/start_all.sh +++ b/start_all.sh @@ -4,4 +4,5 @@ export RUST_BACKTRACE=1 # A simple command to spin up the complete package, ie. both daemons and frontends. # A single 'ctrl+c' stops all processes. -(trap 'kill 0' SIGINT; cargo run --bin maker -- testnet & cargo run --bin taker -- testnet & APP=maker yarn --cwd=./frontend dev & APP=taker yarn --cwd=./frontend dev) +# The maker-id is generated from the makers seed found in daemon/util/testnet_seeds/maker_seed +(trap 'kill 0' SIGINT; cargo run --bin maker -- testnet & cargo run --bin taker -- --maker-id 10d4ba2ac3f7a22da4009d813ff1bc3f404dfe2cc93a32bedf1512aa9951c95e testnet -- & APP=maker yarn --cwd=./frontend dev & APP=taker yarn --cwd=./frontend dev) \ No newline at end of file