diff --git a/protocols/v2/noise-sv2/src/initiator.rs b/protocols/v2/noise-sv2/src/initiator.rs index f3996abe2a..d789aef201 100644 --- a/protocols/v2/noise-sv2/src/initiator.rs +++ b/protocols/v2/noise-sv2/src/initiator.rs @@ -31,7 +31,7 @@ pub struct Initiator { e: Keypair, // upstream pub key #[allow(unused)] - pk: XOnlyPublicKey, + responder_authority_pk: Option, c1: Option, c2: Option, } @@ -96,10 +96,14 @@ impl Initiator { pub fn from_raw_k(key: [u8; 32]) -> Result, Error> { let pk = secp256k1::XOnlyPublicKey::from_slice(&key).map_err(|_| Error::InvalidRawPublicKey)?; - Ok(Self::new(pk)) + Ok(Self::new(Some(pk))) } - pub fn new(pk: XOnlyPublicKey) -> Box { + pub fn without_pk() -> Result, Error> { + Ok(Self::new(None)) + } + + pub fn new(pk: Option) -> Box { let mut self_ = Self { handshake_cipher: None, k: None, @@ -107,7 +111,7 @@ impl Initiator { ck: [0; 32], h: [0; 32], e: Self::generate_key(), - pk, + responder_authority_pk: pk, c1: None, c2: None, }; @@ -226,7 +230,7 @@ impl Initiator { .0 .serialize(); let rs_pk_xonly = XOnlyPublicKey::from_slice(&rs_pub_key).unwrap(); - if signature_message.verify(&rs_pk_xonly) { + if signature_message.verify(&rs_pk_xonly, &self.responder_authority_pk) { let (temp_k1, temp_k2) = Self::hkdf_2(self.get_ck(), &[]); let c1 = ChaCha20Poly1305::new(&temp_k1.into()); let c2 = ChaCha20Poly1305::new(&temp_k2.into()); diff --git a/protocols/v2/noise-sv2/src/responder.rs b/protocols/v2/noise-sv2/src/responder.rs index 15ebe190b2..f5a3ddcc2c 100644 --- a/protocols/v2/noise-sv2/src/responder.rs +++ b/protocols/v2/noise-sv2/src/responder.rs @@ -29,6 +29,8 @@ pub struct Responder { e: Keypair, // Static pub keypair s: Keypair, + // Authority pub keypair + a: Keypair, c1: Option, c2: Option, cert_validity: u32, @@ -107,7 +109,7 @@ impl Responder { } } - pub fn new(s: Keypair, cert_validity: u32) -> Box { + pub fn new(a: Keypair, cert_validity: u32) -> Box { let mut self_ = Self { handshake_cipher: None, k: None, @@ -115,7 +117,8 @@ impl Responder { ck: [0; 32], h: [0; 32], e: Self::generate_key(), - s, + s: Self::generate_key(), + a, c1: None, c2: None, cert_validity, @@ -270,7 +273,7 @@ impl Responder { ret[7] = not_valid_after[1]; ret[8] = not_valid_after[2]; ret[9] = not_valid_after[3]; - SignatureNoiseMessage::sign(&mut ret, &self.s); + SignatureNoiseMessage::sign(&mut ret, &self.s.x_only_public_key().0, &self.a); ret } @@ -294,6 +297,7 @@ impl Responder { } self.e.non_secure_erase(); self.s.non_secure_erase(); + self.a.non_secure_erase(); } } diff --git a/protocols/v2/noise-sv2/src/signature_message.rs b/protocols/v2/noise-sv2/src/signature_message.rs index cacc9a7c61..827199bed9 100644 --- a/protocols/v2/noise-sv2/src/signature_message.rs +++ b/protocols/v2/noise-sv2/src/signature_message.rs @@ -24,27 +24,34 @@ impl From<[u8; 74]> for SignatureNoiseMessage { } impl SignatureNoiseMessage { - pub fn verify(self, pk: &XOnlyPublicKey) -> bool { - let now = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs() as u32; - if self.valid_from <= now && self.not_valid_after >= now { - let secp = Secp256k1::verification_only(); - let (m, s) = self.split(); - let m = Message::from_hashed_data::(&m[0..10]); - let s = match Signature::from_slice(&s) { - Ok(s) => s, - _ => return false, - }; - secp.verify_schnorr(&s, &m, pk).is_ok() + pub fn verify(self, pk: &XOnlyPublicKey, authority_pk: &Option) -> bool { + if let Some(authority_pk) = authority_pk { + let now = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() as u32; + if self.valid_from <= now && self.not_valid_after >= now { + let secp = Secp256k1::verification_only(); + let (m, s) = self.split(); + // m = SHA-256(version || valid_from || not_valid_after || server_static_key) + let m = [&m[0..10], &pk.serialize()].concat(); + let m = Message::from_hashed_data::(&m); + let s = match Signature::from_slice(&s) { + Ok(s) => s, + _ => return false, + }; + secp.verify_schnorr(&s, &m, authority_pk).is_ok() + } else { + false + } } else { - false + true } } - pub fn sign(msg: &mut [u8; 74], kp: &Keypair) { + pub fn sign(msg: &mut [u8; 74], static_pk: &XOnlyPublicKey, kp: &Keypair) { let secp = Secp256k1::signing_only(); - let m = Message::from_hashed_data::(&msg[0..10]); + let m = [&msg[0..10], &static_pk.serialize()].concat(); + let m = Message::from_hashed_data::(&m); let signature = secp.sign_schnorr(&m, kp); for (i, b) in signature.as_ref().iter().enumerate() { msg[10 + i] = *b; diff --git a/protocols/v2/noise-sv2/src/test.rs b/protocols/v2/noise-sv2/src/test.rs index d6925f0a31..3c287fc6a0 100644 --- a/protocols/v2/noise-sv2/src/test.rs +++ b/protocols/v2/noise-sv2/src/test.rs @@ -4,7 +4,7 @@ use crate::{handshake::HandshakeOp, initiator::Initiator, responder::Responder}; fn test_1() { let key_pair = Responder::generate_key(); - let mut initiator = Initiator::new(key_pair.public_key().into()); + let mut initiator = Initiator::new(Some(key_pair.public_key().into())); let mut responder = Responder::new(key_pair, 31449600); let first_message = initiator.step_0().unwrap(); let (second_message, mut codec_responder) = responder.step_1(first_message).unwrap(); diff --git a/roles/jd-client/README.md b/roles/jd-client/README.md index fea7878263..efc5d8d2a1 100644 --- a/roles/jd-client/README.md +++ b/roles/jd-client/README.md @@ -17,8 +17,12 @@ The configuration file contains the following information: 1. The Upstream connection information which includes the SV2 Pool authority public key (`upstream_authority_pubkey`) and the SV2 Pool connection address (`upstream_address`) and port (`upstream_port`). -1. The maximum and minimum SV2 versions (`max_supported_version` and `min_supported_version`) -1. The Job Declarator information which includes the Pool JD connection address (`jd_address`) and the Template Provider connection address to which to connect (`tp_address`). +2. The maximum and minimum SV2 versions (`max_supported_version` and `min_supported_version`) +3. The Job Declarator information which includes the Pool JD connection address (`jd_address`) and the Template Provider connection address to which to connect (`tp_address`). +4. Optionally, you may want to verify that your TP connection is authentic. You may get `tp_authority_public_key` from the logs of your TP, for example: +``` +# 2024-02-13T14:59:24Z Template Provider authority key: EguTM8URcZDQVeEBsM4B5vg9weqEUnufA8pm85fG4bZd +``` ### Run 1. Copy the `jdc-config-example.toml` into `conf/` directory. diff --git a/roles/jd-client/config-examples/jdc-config-hosted-example.toml b/roles/jd-client/config-examples/jdc-config-hosted-example.toml index be04e74a45..e2bebaa44e 100644 --- a/roles/jd-client/config-examples/jdc-config-hosted-example.toml +++ b/roles/jd-client/config-examples/jdc-config-hosted-example.toml @@ -28,7 +28,7 @@ retry = 10 # tp_address = "127.0.0.1:8442" # Hosted testnet TP tp_address = "75.119.150.111:8442" -tp_authority_pub_key = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" +tp_authority_public_key = "3VANfft6ei6jQq1At7d8nmiZzVhBFS4CiQujdgim1ign" # Solo Mining config # List of coinbase outputs used to build the coinbase tx in case of Solo Mining (as last-resort solution of the pools fallback system) diff --git a/roles/jd-client/config-examples/jdc-config-local-example.toml b/roles/jd-client/config-examples/jdc-config-local-example.toml index 06c878514f..66f19bcb04 100644 --- a/roles/jd-client/config-examples/jdc-config-local-example.toml +++ b/roles/jd-client/config-examples/jdc-config-local-example.toml @@ -28,7 +28,6 @@ retry = 10 tp_address = "127.0.0.1:8442" # Hosted testnet TP # tp_address = "75.119.150.111:8442" -tp_authority_pub_key = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" # Solo Mining config # List of coinbase outputs used to build the coinbase tx in case of Solo Mining (as last-resort solution of the pools fallback system) diff --git a/roles/jd-client/src/lib/proxy_config.rs b/roles/jd-client/src/lib/proxy_config.rs index ec456b1a2a..89c97a7beb 100644 --- a/roles/jd-client/src/lib/proxy_config.rs +++ b/roles/jd-client/src/lib/proxy_config.rs @@ -36,7 +36,7 @@ pub struct ProxyConfig { pub authority_secret_key: Secp256k1SecretKey, pub cert_validity_sec: u64, pub tp_address: String, - pub tp_authority_pub_key: Secp256k1PublicKey, + pub tp_authority_public_key: Option, pub retry: u32, pub upstreams: Vec, #[serde(deserialize_with = "duration_from_toml")] diff --git a/roles/jd-client/src/lib/template_receiver/mod.rs b/roles/jd-client/src/lib/template_receiver/mod.rs index aad2f1ac37..93e299b0c9 100644 --- a/roles/jd-client/src/lib/template_receiver/mod.rs +++ b/roles/jd-client/src/lib/template_receiver/mod.rs @@ -53,7 +53,7 @@ impl TemplateRx { task_collector: Arc>>, pool_chaneger_trigger: Arc>, miner_coinbase_outputs: Vec, - authority_public_key: Secp256k1PublicKey, + authority_public_key: Option, test_only_do_not_send_solution_to_tp: bool, ) { let mut encoded_outputs = vec![]; @@ -62,8 +62,11 @@ impl TemplateRx { .expect("Invalid coinbase output in config"); let stream = tokio::net::TcpStream::connect(address).await.unwrap(); - let pub_key: Secp256k1PublicKey = authority_public_key; - let initiator = Initiator::from_raw_k(pub_key.into_bytes()).unwrap(); + let initiator = match authority_public_key { + Some(pub_key) => Initiator::from_raw_k(pub_key.into_bytes()), + None => Initiator::without_pk(), + } + .unwrap(); let (mut receiver, mut sender, _, _) = Connection::new(stream, HandshakeRole::Initiator(initiator)) .await diff --git a/roles/jd-client/src/main.rs b/roles/jd-client/src/main.rs index 9a6ba2dbe8..ac246987ed 100644 --- a/roles/jd-client/src/main.rs +++ b/roles/jd-client/src/main.rs @@ -249,7 +249,7 @@ async fn initialize_jd_as_solo_miner( task_collector, Arc::new(Mutex::new(PoolChangerTrigger::new(timeout))), miner_tx_out.clone(), - proxy_config.tp_authority_pub_key, + proxy_config.tp_authority_public_key, false, ) .await; @@ -384,7 +384,7 @@ async fn initialize_jd( task_collector, Arc::new(Mutex::new(PoolChangerTrigger::new(timeout))), vec![], - proxy_config.tp_authority_pub_key, + proxy_config.tp_authority_public_key, test_only_do_not_send_solution_to_tp, ) .await; diff --git a/roles/pool/README.md b/roles/pool/README.md index 359020f0eb..5d7bd5b507 100644 --- a/roles/pool/README.md +++ b/roles/pool/README.md @@ -27,6 +27,10 @@ The configuration file contains the following information: 1. The SRI Pool information which includes the SRI Pool authority public key (`authority_pubkey`), the SRI Pool authority secret key (`authority_secret_key`), along with its certificate validity (`cert_validity_sec`). In addition to this, it contains the address which it will use to listen to new connection from downstream roles (`listen_address`) and the list of uncompressed pubkeys for coinbase payout (`coinbase_outputs`). 2. The SRI Pool Job Negatiator information which includes the Template Provider address (`tp_address`) and the address it uses to listen new request from the downstream JDs (`jd_address`). +3. Optionally, you may want to verify that your TP connection is authentic. You may get `tp_authority_public_key` from the logs of your TP, for example: +``` +# 2024-02-13T14:59:24Z Template Provider authority key: EguTM8URcZDQVeEBsM4B5vg9weqEUnufA8pm85fG4bZd +``` ### Run 1. Copy the `pool-config-example.toml` into `conf/` directory. diff --git a/roles/pool/config-examples/pool-config-hosted-tp-example.toml b/roles/pool/config-examples/pool-config-hosted-tp-example.toml index bc0f915da4..a86c600d59 100644 --- a/roles/pool/config-examples/pool-config-hosted-tp-example.toml +++ b/roles/pool/config-examples/pool-config-hosted-tp-example.toml @@ -25,3 +25,4 @@ pool_signature = "Stratum v2 SRI Pool" #tp_address = "127.0.0.1:8442" # Hosted testnet TP tp_address = "75.119.150.111:8442" +tp_authority_public_key = "EguTM8URcZDQVeEBsM4B5vg9weqEUnufA8pm85fG4bZd" \ No newline at end of file diff --git a/roles/pool/config-examples/pool-config-local-tp-example.toml b/roles/pool/config-examples/pool-config-local-tp-example.toml index 5c94d56087..016c372f59 100644 --- a/roles/pool/config-examples/pool-config-local-tp-example.toml +++ b/roles/pool/config-examples/pool-config-local-tp-example.toml @@ -24,5 +24,3 @@ pool_signature = "Stratum v2 SRI Pool" # Template Provider config # Local TP (this is pointing to localhost so you must run a TP locally for this configuration to work) tp_address = "127.0.0.1:8442" -# Hosted testnet TP -# tp_address = "75.119.150.111:8442" diff --git a/roles/pool/src/lib/mining_pool/mod.rs b/roles/pool/src/lib/mining_pool/mod.rs index d7a6c15fb2..86c85e80aa 100644 --- a/roles/pool/src/lib/mining_pool/mod.rs +++ b/roles/pool/src/lib/mining_pool/mod.rs @@ -83,6 +83,7 @@ impl TryFrom<&CoinbaseOutput> for CoinbaseOutput_ { pub struct Configuration { pub listen_address: String, pub tp_address: String, + pub tp_authority_public_key: Option, pub authority_public_key: Secp256k1PublicKey, pub authority_secret_key: Secp256k1SecretKey, pub cert_validity_sec: u64, diff --git a/roles/pool/src/lib/template_receiver/mod.rs b/roles/pool/src/lib/template_receiver/mod.rs index 5ab06c56a3..a996852a86 100644 --- a/roles/pool/src/lib/template_receiver/mod.rs +++ b/roles/pool/src/lib/template_receiver/mod.rs @@ -43,13 +43,17 @@ impl TemplateRx { message_received_signal: Receiver<()>, status_tx: status::Sender, coinbase_out_len: u32, - authority_public_key: Secp256k1PublicKey, + expected_tp_authority_public_key: Option, ) -> PoolResult<()> { let stream = TcpStream::connect(address).await?; info!("Connected to template distribution server at {}", address); - let pub_key: Secp256k1PublicKey = authority_public_key; - let initiator = Initiator::from_raw_k(pub_key.into_bytes())?; + let initiator = match expected_tp_authority_public_key { + Some(expected_tp_authority_public_key) => { + Initiator::from_raw_k(expected_tp_authority_public_key.into_bytes()) + } + None => Initiator::without_pk(), + }?; let (mut receiver, mut sender, _, _) = Connection::new(stream, HandshakeRole::Initiator(initiator)) .await diff --git a/roles/pool/src/main.rs b/roles/pool/src/main.rs index 73280852c5..169243c235 100644 --- a/roles/pool/src/main.rs +++ b/roles/pool/src/main.rs @@ -115,7 +115,7 @@ async fn main() { return; } }; - let authority_public_key = config.authority_public_key; + let tp_authority_public_key = config.tp_authority_public_key; let template_rx_res = TemplateRx::connect( config.tp_address.parse().unwrap(), s_new_t, @@ -124,7 +124,7 @@ async fn main() { r_message_recv_signal, status::Sender::Upstream(status_tx.clone()), coinbase_output_len, - authority_public_key, + tp_authority_public_key, ) .await;