diff --git a/teos-common/Cargo.toml b/teos-common/Cargo.toml index be0cb489..20a5f6a1 100644 --- a/teos-common/Cargo.toml +++ b/teos-common/Cargo.toml @@ -24,4 +24,7 @@ bitcoin = { version = "0.28.0", features = [ "use-serde" ] } lightning = "0.0.108" [build-dependencies] -tonic-build = "0.6" \ No newline at end of file +tonic-build = "0.6" + +[features] +notAccountable = [] \ No newline at end of file diff --git a/teos-common/proto/common/teos/v2/user.proto b/teos-common/proto/common/teos/v2/user.proto index 84e54d7b..253ab56b 100644 --- a/teos-common/proto/common/teos/v2/user.proto +++ b/teos-common/proto/common/teos/v2/user.proto @@ -14,7 +14,7 @@ message RegisterRequest { uint32 available_slots = 2; uint32 subscription_start = 3; uint32 subscription_expiry = 4; - string subscription_signature = 5; + optional string subscription_signature = 5; } message GetSubscriptionInfoRequest { diff --git a/teos-common/src/receipts.rs b/teos-common/src/receipts.rs index 6e806c22..cf6e9cb9 100644 --- a/teos-common/src/receipts.rs +++ b/teos-common/src/receipts.rs @@ -95,7 +95,7 @@ impl RegistrationReceipt { // TODO: Check if there's any case where this can actually fail. Don't unwrap if so. self.signature = Some(cryptography::sign(&self.to_vec(), sk).unwrap()); } - +#[cfg(not(feature = "notAccountable"))] pub fn verify(&self, id: &UserId) -> bool { if let Some(signature) = self.signature() { cryptography::verify(&self.to_vec(), &signature, &id.0) @@ -103,6 +103,11 @@ impl RegistrationReceipt { false } } + #[cfg(feature = "notAccountable")] + pub fn verify(&self, id: &UserId) -> bool { + true + } + } /// Proof that a certain state was backed up with the tower. diff --git a/teos/Cargo.toml b/teos/Cargo.toml index b062b467..785b11d1 100644 --- a/teos/Cargo.toml +++ b/teos/Cargo.toml @@ -51,3 +51,7 @@ jsonrpc-http-server = "17.1.0" rand = "0.8.4" tempdir = "0.3.7" tokio-stream = { version = "0.1.5", features = [ "net" ] } + +[features] +notAccountable = [] + diff --git a/teos/data_dir/ca-key.pem b/teos/data_dir/ca-key.pem new file mode 100644 index 00000000..ff9d6114 --- /dev/null +++ b/teos/data_dir/ca-key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgtN56b2TvxnxpxbGr +MYsxYa0XL3Z9hHqAMALlLMsjVLGhRANCAASosRDTebN0QMAwYf95myHKY6EF/t4M +cGQ6SSa/ZEgxTmy5k/GQYuXzy+ly7wJKf206yO8GzRx/1WlpX3FT3jaD +-----END PRIVATE KEY----- diff --git a/teos/data_dir/ca.pem b/teos/data_dir/ca.pem new file mode 100644 index 00000000..b1f44296 --- /dev/null +++ b/teos/data_dir/ca.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBdDCCARqgAwIBAgIJALLz0HV76fWzMAoGCCqGSM49BAMCMBcxFTATBgNVBAMM +DHRlb3MgUm9vdCBDQTAgFw03NTAxMDEwMDAwMDBaGA80MDk2MDEwMTAwMDAwMFow +FzEVMBMGA1UEAwwMdGVvcyBSb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD +QgAEqLEQ03mzdEDAMGH/eZshymOhBf7eDHBkOkkmv2RIMU5suZPxkGLl88vpcu8C +Sn9tOsjvBs0cf9VpaV9xU942g6NNMEswGQYDVR0RBBIwEIIDY2xugglsb2NhbGhv +c3QwHQYDVR0OBBYEFLP16Xt10POyIGAdVbTbEqiEKiXmMA8GA1UdEwEB/wQFMAMB +Af8wCgYIKoZIzj0EAwIDSAAwRQIgT5ZKkWywur6OiyUkjAzgcg0SipU9HDKnAYis +GZfqLjUCIQCy2FqPqsO4rFb7JkP2fpSu4l3fQJY8g+ZeEyK2VucLEg== +-----END CERTIFICATE----- diff --git a/teos/data_dir/client-key.pem b/teos/data_dir/client-key.pem new file mode 100644 index 00000000..ac6c474a --- /dev/null +++ b/teos/data_dir/client-key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg12hR3kPQnrrhTSEM +yHkDoblFg/EC/XtbOmRo2ZKdeA2hRANCAATwfs9UQfAWUBjmgi1/oQ54gYcBnbSP +ZobEn2pDy+YU6Rh2FLway8Ytne1xQ6iowSh1EIH2XWYuWNDtSeVvv9ln +-----END PRIVATE KEY----- diff --git a/teos/data_dir/client.pem b/teos/data_dir/client.pem new file mode 100644 index 00000000..ef4b1f58 --- /dev/null +++ b/teos/data_dir/client.pem @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBRzCB7qADAgECAgkAmlmq3GzgOMYwCgYIKoZIzj0EAwIwFzEVMBMGA1UEAwwM +dGVvcyBSb290IENBMCAXDTc1MDEwMTAwMDAwMFoYDzQwOTYwMTAxMDAwMDAwWjAb +MRkwFwYDVQQDDBB0ZW9zIGdycGMgQ2xpZW50MFkwEwYHKoZIzj0CAQYIKoZIzj0D +AQcDQgAE8H7PVEHwFlAY5oItf6EOeIGHAZ20j2aGxJ9qQ8vmFOkYdhS8GsvGLZ3t +cUOoqMEodRCB9l1mLljQ7Unlb7/ZZ6MdMBswGQYDVR0RBBIwEIIDY2xugglsb2Nh +bGhvc3QwCgYIKoZIzj0EAwIDSAAwRQIhAIROzv0qei4YsFywUHtyhQv+JEDzKawP +hL5kZu2uzlwvAiBZU1TG3LLgm/YumqzFSEt6cs5qM4r93Y7vHaVZQZ0I8w== +-----END CERTIFICATE----- diff --git a/teos/data_dir/regtest/teos_db.sql3 b/teos/data_dir/regtest/teos_db.sql3 new file mode 100644 index 00000000..06418f31 Binary files /dev/null and b/teos/data_dir/regtest/teos_db.sql3 differ diff --git a/teos/data_dir/server-key.pem b/teos/data_dir/server-key.pem new file mode 100644 index 00000000..de97ecef --- /dev/null +++ b/teos/data_dir/server-key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgQkEWrVRyGsV3qJN+ +qMQIjva5uRJSkHFfWj/jN5NE5aahRANCAARBSZchT2XlHvWAucSDQ4rziyBfT86k +88asPnrqBPn1tgDa8FG1gGzuKShwm5cbmq8D7BqSYvIW9FO4TI6yb5iX +-----END PRIVATE KEY----- diff --git a/teos/data_dir/server.pem b/teos/data_dir/server.pem new file mode 100644 index 00000000..65d3925b --- /dev/null +++ b/teos/data_dir/server.pem @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBRzCB7qADAgECAgkA7vIgeKbbSRMwCgYIKoZIzj0EAwIwFzEVMBMGA1UEAwwM +dGVvcyBSb290IENBMCAXDTc1MDEwMTAwMDAwMFoYDzQwOTYwMTAxMDAwMDAwWjAb +MRkwFwYDVQQDDBB0ZW9zIGdycGMgU2VydmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0D +AQcDQgAEQUmXIU9l5R71gLnEg0OK84sgX0/OpPPGrD566gT59bYA2vBRtYBs7iko +cJuXG5qvA+wakmLyFvRTuEyOsm+Yl6MdMBswGQYDVR0RBBIwEIIDY2xugglsb2Nh +bGhvc3QwCgYIKoZIzj0EAwIDSAAwRQIgNHocDiEeyM1uwCSxrFkdEeQR6GOyVw8v +mzb4eOLRTusCIQDFg4YzVZFwMz5A/8zeNT9QO3OVOEBfaVrv5CBoef11ew== +-----END CERTIFICATE----- diff --git a/teos/data_dir/teos.toml b/teos/data_dir/teos.toml new file mode 100644 index 00000000..93f5ee9e --- /dev/null +++ b/teos/data_dir/teos.toml @@ -0,0 +1,33 @@ +# API +api_bind = "127.0.0.1" +api_port = 9814 +tor_control_port = 9051 +onion_hidden_service_port = 9814 +tor_support = false + +# RPC +rpc_bind = "127.0.0.1" +rpc_port = 8814 + +# bitcoind +btc_network = "regtest" +btc_rpc_user = "exch" +btc_rpc_password = "goodpass" +btc_rpc_connect = "localhost" +btc_rpc_port = 18443 + +# Flags +debug = false +deps_debug = false +overwrite_key = false + +# General +subscription_slots = 10000 +subscription_duration = 4320 +expiry_delta = 6 +min_to_self_delay = 20 +polling_delta = 60 + +# Internal API +internal_api_bind = "127.0.0.1" +internal_api_port = 50051 \ No newline at end of file diff --git a/teos/src/api/internal.rs b/teos/src/api/internal.rs index c6fa355d..ae747a23 100644 --- a/teos/src/api/internal.rs +++ b/teos/src/api/internal.rs @@ -67,34 +67,66 @@ impl InternalAPI { #[tonic::async_trait] impl PublicTowerServices for Arc { /// Register endpoint. Part of the public API. Internally calls [Watcher::register]. - async fn register( - &self, - request: Request, - ) -> Result, Status> { - self.check_service_unavailable()?; - let req_data = request.into_inner(); - - let user_id = UserId::from_slice(&req_data.user_id).map_err(|_| { - Status::new( - Code::InvalidArgument, - "Provided public key does not match expected format (33-byte compressed key)", - ) - })?; + #[cfg(not(feature = "notAccountable"))] +async fn register( + &self, + request: Request, +) -> Result, Status> { + self.check_service_unavailable()?; + let req_data = request.into_inner(); + + let user_id = UserId::from_slice(&req_data.user_id).map_err(|_| { + Status::new( + Code::InvalidArgument, + "Provided public key does not match expected format (33-byte compressed key)", + ) + })?; + + match self.watcher.register(user_id) { + Ok(receipt) => Ok(Response::new(common_msgs::RegisterResponse { + user_id: req_data.user_id, + available_slots: receipt.available_slots(), + subscription_start: receipt.subscription_start(), + subscription_expiry: receipt.subscription_expiry(), + subscription_signature: receipt.signature().unwrap(), + })), + Err(_) => Err(Status::new( + Code::ResourceExhausted, + "Subscription maximum slots count reached", + )), + } +} - match self.watcher.register(user_id) { - Ok(receipt) => Ok(Response::new(common_msgs::RegisterResponse { - user_id: req_data.user_id, - available_slots: receipt.available_slots(), - subscription_start: receipt.subscription_start(), - subscription_expiry: receipt.subscription_expiry(), - subscription_signature: receipt.signature().unwrap(), - })), - Err(_) => Err(Status::new( - Code::ResourceExhausted, - "Subscription maximum slots count reached", - )), - } +#[cfg(feature = "notAccountable")] +async fn register( + &self, + request: Request, +) -> Result, Status> { + self.check_service_unavailable()?; + let req_data = request.into_inner(); + + let user_id = UserId::from_slice(&req_data.user_id).map_err(|_| { + Status::new( + Code::InvalidArgument, + "Provided public key does not match expected format (33-byte compressed key)", + ) + })?; + + match self.watcher.register(user_id) { + Ok(receipt) => Ok(Response::new(common_msgs::RegisterResponse { + user_id: req_data.user_id, + available_slots: receipt.available_slots(), + subscription_start: receipt.subscription_start(), + subscription_expiry: receipt.subscription_expiry(), + subscription_signature: None, + })), + Err(_) => Err(Status::new( + Code::ResourceExhausted, + "Subscription maximum slots count reached", + )), } +} + /// Add appointment endpoint. Part of the public API. Internally calls [Watcher::add_appointment]. async fn add_appointment( diff --git a/teos/src/watcher.rs b/teos/src/watcher.rs index ec2e7da5..3a23ac57 100644 --- a/teos/src/watcher.rs +++ b/teos/src/watcher.rs @@ -171,12 +171,20 @@ impl Watcher { /// Registers a new user within the [Watcher]. This request is passed to the [Gatekeeper], who is in /// charge of managing users. + #[cfg(not(feature = "notAccountable"))] pub(crate) fn register(&self, user_id: UserId) -> Result { let mut receipt = self.gatekeeper.add_update_user(user_id)?; receipt.sign(&self.signing_key); - + + Ok(receipt) + } + + #[cfg(feature = "notAccountable")] + pub(crate) fn register(&self, user_id: UserId) -> Result { + let receipt = self.gatekeeper.add_update_user(user_id)?; Ok(receipt) } + /// Adds a new [Appointment] to the tower. /// diff --git a/watchtower-plugin/Cargo.toml b/watchtower-plugin/Cargo.toml index 976f8e9d..7db67753 100755 --- a/watchtower-plugin/Cargo.toml +++ b/watchtower-plugin/Cargo.toml @@ -33,4 +33,7 @@ teos-common = { path = "../teos-common" } [dev-dependencies] mockito = "0.32.4" -tempdir = "0.3.7" \ No newline at end of file +tempdir = "0.3.7" + +[features] +notAccountable = [] diff --git a/watchtower-plugin/src/dbm.rs b/watchtower-plugin/src/dbm.rs index bf271bb8..df002422 100755 --- a/watchtower-plugin/src/dbm.rs +++ b/watchtower-plugin/src/dbm.rs @@ -144,6 +144,7 @@ impl DBM { /// /// This function MUST be guarded against inserting duplicate (tower_id, subscription_expiry) pairs. /// This is currently done in WTClient::add_update_tower. + #[cfg(not(feature = "notAccountable"))] pub fn store_tower_record( &mut self, tower_id: TowerId, @@ -165,7 +166,28 @@ impl DBM { tx.commit().map_err(Error::Unknown) } + #[cfg(feature = "notAccountable")] + pub fn store_tower_record( + &mut self, + tower_id: TowerId, + net_addr: &str, + receipt: &RegistrationReceipt, + ) -> Result<(), Error> { + let tx = self.get_mut_connection().transaction().unwrap(); + tx.execute( + "INSERT INTO towers (tower_id, net_addr, available_slots) + VALUES (?1, ?2, ?3) + ON CONFLICT (tower_id) DO UPDATE SET net_addr = ?2, available_slots = ?3", + params![tower_id.to_vec(), net_addr, receipt.available_slots()], + ) + .map_err(Error::Unknown)?; + tx.execute( + "INSERT INTO registration_receipts (tower_id, available_slots, subscription_start, subscription_expiry) + VALUES (?1, ?2, ?3, ?4)", + params![tower_id.to_vec(), receipt.available_slots(), receipt.subscription_start(), receipt.subscription_expiry()]).map_err( Error::Unknown)?; + tx.commit().map_err(Error::Unknown) + } /// Loads a tower record from the database. /// /// Tower records are composed from the tower information and the appointment data. The latter is split in: @@ -212,6 +234,7 @@ impl DBM { /// Loads the latest registration receipt for a given tower. /// /// Latests is determined by the one with the `subscription_expiry` further into the future. + #[cfg(not(feature = "notAccountable"))] pub fn load_registration_receipt( &self, tower_id: TowerId, @@ -241,6 +264,35 @@ impl DBM { .ok() } + #[cfg(feature = "notAccountable")] + + pub fn load_registration_receipt( + &self, + tower_id: TowerId, + user_id: UserId, + ) -> Option { + let mut stmt = self + .connection + .prepare( + "SELECT available_slots, subscription_start, subscription_expiry + FROM registration_receipts + WHERE tower_id = ?1 AND subscription_expiry = (SELECT MAX(subscription_expiry) + FROM registration_receipts + WHERE tower_id = ?1)", + ) + .unwrap(); + + stmt.query_row([tower_id.to_vec()], |row| { + let slots: u32 = row.get(0).unwrap(); + let start: u32 = row.get(1).unwrap(); + let expiry: u32 = row.get(2).unwrap(); + + Ok(RegistrationReceipt::new( + user_id, slots, start, expiry, + )) + }) + .ok() + } /// Removes a tower record from the database. /// /// This triggers a cascade deletion of all related data, such as appointments, appointment receipts, etc. As long as there is a single diff --git a/watchtower-plugin/src/net/http.rs b/watchtower-plugin/src/net/http.rs index 1a93633d..8b18d531 100644 --- a/watchtower-plugin/src/net/http.rs +++ b/watchtower-plugin/src/net/http.rs @@ -56,6 +56,7 @@ impl From for AddAppointmentError { } /// Handles the logic of interacting with the `register` endpoint of the tower. +#[cfg(not(feature = "notAccountable"))] pub async fn register( tower_id: TowerId, user_id: UserId, @@ -85,6 +86,35 @@ pub async fn register( ) }) } +#[cfg(feature = "notAccountable")] +pub async fn register( + tower_id: TowerId, + user_id: UserId, + tower_net_addr: &NetAddr, + proxy: &Option, +) -> Result { + log::info!("Registering in the Eye of Satoshi (tower_id={tower_id})"); + process_post_response( + post_request( + tower_net_addr, + Endpoint::Register, + &common_msgs::RegisterRequest { + user_id: user_id.to_vec(), + }, + proxy, + ) + .await, + ) + .await + .map(|r: common_msgs::RegisterResponse| { + RegistrationReceipt::new( + user_id, + r.available_slots, + r.subscription_start, + r.subscription_expiry + ) + }) +} /// Encapsulates the logging and response parsing of sending and appointment to the tower. pub async fn add_appointment(