diff options
-rw-r--r-- | CMakeLists.txt | 69 | ||||
-rw-r--r-- | CMakePresets.json | 17 | ||||
-rw-r--r-- | include/client/web_socket_client.hpp | 4 | ||||
-rw-r--r-- | include/client/websocketpp_client.hpp | 44 | ||||
-rw-r--r-- | include/data/data.hpp | 3 | ||||
-rw-r--r-- | include/nostr.hpp | 153 | ||||
-rw-r--r-- | include/nostr_service_base.hpp | 104 | ||||
-rw-r--r-- | include/signer/noscrypt_signer.hpp | 55 | ||||
-rw-r--r-- | include/signer/signer.hpp | 7 | ||||
-rw-r--r-- | src/client/websocketpp_client.cpp | 193 | ||||
-rw-r--r-- | src/data/event.cpp | 6 | ||||
-rw-r--r-- | src/nostr_service_base.cpp (renamed from src/nostr_service.cpp) | 119 | ||||
-rw-r--r-- | src/signer/noscrypt_signer.cpp | 274 | ||||
-rw-r--r-- | test/nostr_service_base_test.cpp (renamed from test/nostr_service_test.cpp) | 185 |
14 files changed, 622 insertions, 611 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index e8be787..461fbcc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,9 +75,12 @@ include_directories(${SIGNER_INCLUDE_DIR}) set(HEADERS ${INCLUDE_DIR}/nostr.hpp + ${INCLUDE_DIR}/nostr_service_base.hpp ${CLIENT_INCLUDE_DIR}/web_socket_client.hpp + ${CLIENT_INCLUDE_DIR}/websocketpp_client.hpp ${DATA_INCLUDE_DIR}/data.hpp ${SIGNER_INCLUDE_DIR}/signer.hpp + ${SIGNER_INCLUDE_DIR}/noscrypt_signer.hpp ) set(SOURCE_DIR ./src) @@ -85,7 +88,7 @@ set(CLIENT_SOURCE_DIR ./src/client) set(DATA_SOURCE_DIR ./src/data) set(SIGNER_SOURCE_DIR ./src/signer) set(SOURCES - ${SOURCE_DIR}/nostr_service.cpp + ${SOURCE_DIR}/nostr_service_base.cpp ${CLIENT_SOURCE_DIR}/websocketpp_client.cpp ${DATA_SOURCE_DIR}/event.cpp ${DATA_SOURCE_DIR}/filters.cpp @@ -104,33 +107,37 @@ target_link_libraries(aedile PRIVATE set_target_properties(aedile PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS YES) #======== Build the tests ========# -enable_testing() -include(GoogleTest) - -FetchContent_Declare( - googletest - URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip -) - -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(googletest) - -enable_testing() - -set(TEST_DIR ./test) -set(TEST_SOURCES - ${TEST_DIR}/nostr_service_test.cpp -) - -add_executable(aedile_test ${TEST_SOURCES} ${HEADERS}) -target_link_libraries(aedile_test PRIVATE - GTest::gmock - GTest::gtest - GTest::gtest_main - plog::plog - websocketpp::websocketpp - aedile -) -set_target_properties(aedile_test PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS YES) - -gtest_add_tests(TARGET aedile_test) +if(AEDILE_INCLUDE_TESTS) + message(STATUS "Building unit tests.") + + enable_testing() + include(GoogleTest) + + FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip + ) + + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + FetchContent_MakeAvailable(googletest) + + enable_testing() + + set(TEST_DIR ./test) + set(TEST_SOURCES + ${TEST_DIR}/nostr_service_base_test.cpp + ) + + add_executable(aedile_test ${TEST_SOURCES} ${HEADERS}) + target_link_libraries(aedile_test PRIVATE + GTest::gmock + GTest::gtest + GTest::gtest_main + plog::plog + websocketpp::websocketpp + aedile + ) + set_target_properties(aedile_test PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS YES) + + gtest_add_tests(TARGET aedile_test) +endif() diff --git a/CMakePresets.json b/CMakePresets.json index 208a085..84c0aff 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -9,6 +9,16 @@ "CMAKE_BUILD_TYPE": "Release", "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" } + }, + { + "name": "linux tests", + "generator": "Unix Makefiles", + "binaryDir": "${sourceDir}/build/linux", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", + "AEDILE_INCLUDE_TESTS": "ON" + } } ], "buildPresets": [ @@ -16,12 +26,17 @@ "name": "linux", "configurePreset": "linux", "jobs": 4 + }, + { + "name": "linux tests", + "configurePreset": "linux tests", + "jobs": 4 } ], "testPresets": [ { "name": "linux", - "configurePreset": "linux" + "configurePreset": "linux tests" } ] } diff --git a/include/client/web_socket_client.hpp b/include/client/web_socket_client.hpp index eca8e24..19fc949 100644 --- a/include/client/web_socket_client.hpp +++ b/include/client/web_socket_client.hpp @@ -10,14 +10,14 @@ namespace nostr { namespace client { -class IWebSocketClient; - /** * @brief An interface for a WebSocket client singleton. */ class IWebSocketClient { public: + virtual ~IWebSocketClient() = default; + /** * @brief Starts the client. * @remark This method must be called before any other client methods. diff --git a/include/client/websocketpp_client.hpp b/include/client/websocketpp_client.hpp new file mode 100644 index 0000000..becf4fa --- /dev/null +++ b/include/client/websocketpp_client.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "web_socket_client.hpp" + +namespace nostr +{ +namespace client +{ +/** + * @brief An implementation of the `IWebSocketClient` interface that uses the WebSocket++ library. + */ +class WebsocketppClient : public IWebSocketClient +{ +public: + void start() override; + + void stop() override; + + void openConnection(std::string uri) override; + + bool isConnected(std::string uri) override; + + std::tuple<std::string, bool> send(std::string message, std::string uri) override; + + std::tuple<std::string, bool> send( + std::string message, + std::string uri, + std::function<void(const std::string&)> messageHandler) override; + + void receive(std::string uri, std::function<void(const std::string&)> messageHandler) override; + + void closeConnection(std::string uri) override; + +private: + typedef websocketpp::client<websocketpp::config::asio_client> websocketpp_client; + typedef std::unordered_map<std::string, websocketpp::connection_hdl>::iterator connection_hdl_iterator; + + websocketpp_client _client; + std::unordered_map<std::string, websocketpp::connection_hdl> _connectionHandles; + std::mutex _propertyMutex; +}; +} // namespace client +} // namespace nostr + diff --git a/include/data/data.hpp b/include/data/data.hpp index 78c17c4..46156cd 100644 --- a/include/data/data.hpp +++ b/include/data/data.hpp @@ -13,9 +13,6 @@ namespace nostr { namespace data { -class Event; -class Filters; - /** * @brief A Nostr event. * @remark All data transmitted over the Nostr protocol is encoded in JSON blobs. This struct diff --git a/include/nostr.hpp b/include/nostr.hpp index d21a86d..c555bbe 100644 --- a/include/nostr.hpp +++ b/include/nostr.hpp @@ -21,52 +21,35 @@ namespace nostr { -class NostrService; - // TODO: Create custom exception types for the nostr namespace. -class NostrService +class INostrServiceBase { public: - NostrService( - std::shared_ptr<plog::IAppender> appender, - std::shared_ptr<client::IWebSocketClient> client, - std::shared_ptr<signer::ISigner> signer); - NostrService( - std::shared_ptr<plog::IAppender> appender, - std::shared_ptr<client::IWebSocketClient> client, - std::shared_ptr<signer::ISigner> signer, - std::vector<std::string> relays); - ~NostrService(); - - std::vector<std::string> defaultRelays() const; - - std::vector<std::string> activeRelays() const; - - std::unordered_map<std::string, std::vector<std::string>> subscriptions() const; + virtual ~INostrServiceBase() = default; /** * @brief Opens connections to the default Nostr relays of the instance, as specified in * the constructor. * @return A list of the relay URLs to which connections were successfully opened. */ - std::vector<std::string> openRelayConnections(); + virtual std::vector<std::string> openRelayConnections() = 0; /** * @brief Opens connections to the specified Nostr relays. * @returns A list of the relay URLs to which connections were successfully opened. */ - std::vector<std::string> openRelayConnections(std::vector<std::string> relays); + virtual std::vector<std::string> openRelayConnections(std::vector<std::string> relays) = 0; /** * @brief Closes all open relay connections. */ - void closeRelayConnections(); + virtual void closeRelayConnections() = 0; /** * @brief Closes any open connections to the specified Nostr relays. */ - void closeRelayConnections(std::vector<std::string> relays); + virtual void closeRelayConnections(std::vector<std::string> relays) = 0; /** * @brief Publishes a Nostr event to all open relay connections. @@ -74,7 +57,8 @@ public: * to which relays the event was published successfully, and to which relays the event failed * to publish. */ - std::tuple<std::vector<std::string>, std::vector<std::string>> publishEvent(std::shared_ptr<data::Event> event); + virtual std::tuple<std::vector<std::string>, std::vector<std::string>> publishEvent( + std::shared_ptr<data::Event> event) = 0; /** * @brief Queries all open relay connections for events matching the given set of filters, and @@ -88,7 +72,8 @@ public: * set on the filters in the range 1-64, inclusive. If no valid limit is given, it will be * defaulted to 16. */ - std::vector<std::shared_ptr<data::Event>> queryRelays(std::shared_ptr<data::Filters> filters); + virtual std::vector<std::shared_ptr<data::Event>> queryRelays( + std::shared_ptr<data::Filters> filters) = 0; /** * @brief Queries all open relay connections for events matching the given set of filters. @@ -104,11 +89,11 @@ public: * events returned from the relay for the given filters. The service will not store the * events, and they will not be accessible via `getNewEvents`. */ - std::string queryRelays( + virtual std::string queryRelays( std::shared_ptr<data::Filters> filters, std::function<void(const std::string&, std::shared_ptr<data::Event>)> eventHandler, std::function<void(const std::string&)> eoseHandler, - std::function<void(const std::string&, const std::string&)> closeHandler); + std::function<void(const std::string&, const std::string&)> closeHandler) = 0; /** * @brief Closes the subscription with the given ID on all open relay connections. @@ -116,7 +101,8 @@ public: * to which relays the message was sent successfully, and which relays failed to receive the * message. */ - std::tuple<std::vector<std::string>, std::vector<std::string>> closeSubscription(std::string subscriptionId); + virtual std::tuple<std::vector<std::string>, std::vector<std::string>> closeSubscription( + std::string subscriptionId) = 0; /** * @brief Closes the subscription with the given ID on the given relay. @@ -124,119 +110,12 @@ public: * @remark If the subscription does not exist on the given relay, or if the relay is not * connected, the method will do nothing and return false. */ - bool closeSubscription(std::string subscriptionId, std::string relay); + virtual bool closeSubscription(std::string subscriptionId, std::string relay) = 0; /** * @brief Closes all open subscriptions on all open relay connections. * @returns A list of any subscription IDs that failed to close. */ - std::vector<std::string> closeSubscriptions(); - - /** - * @brief Closes all open subscriptions on the given relays. - * @returns A list of any subscription IDs that failed to close. - */ - std::vector<std::string> closeSubscriptions(std::vector<std::string> relays); - -private: - ///< The maximum number of events the service will store for each subscription. - const int MAX_EVENTS_PER_SUBSCRIPTION = 128; - - ///< The WebSocket client used to communicate with relays. - std::shared_ptr<client::IWebSocketClient> _client; - ///< The signer used to sign Nostr events. - std::shared_ptr<signer::ISigner> _signer; - - ///< A mutex to protect the instance properties. - std::mutex _propertyMutex; - ///< The default set of Nostr relays to which the service will attempt to connect. - std::vector<std::string> _defaultRelays; - ///< The set of Nostr relays to which the service is currently connected. - std::vector<std::string> _activeRelays; - ///< A map from subscription IDs to the relays on which each subscription is open. - std::unordered_map<std::string, std::vector<std::string>> _subscriptions; - - /** - * @brief Determines which of the given relays are currently connected. - * @returns A list of the URIs of currently-open relay connections from the given list. - */ - std::vector<std::string> getConnectedRelays(std::vector<std::string> relays); - - /** - * @brief Determines which of the given relays are not currently connected. - * @returns A list of the URIs of currently-unconnected relays from the given list. - */ - std::vector<std::string> getUnconnectedRelays(std::vector<std::string> relays); - - /** - * @brief Determines whether the given relay is currently connected. - * @returns True if the relay is connected, false otherwise. - */ - bool isConnected(std::string relay); - - /** - * @brief Removes the given relay from the instance's list of active relays. - */ - void eraseActiveRelay(std::string relay); - - /** - * @brief Opens a connection from the client to the given relay. - */ - void connect(std::string relay); - - /** - * @brief Closes the connection from the client to the given relay. - */ - void disconnect(std::string relay); - - /** - * @brief Generates a unique subscription ID that may be used to identify event requests. - * @returns A stringified UUID. - */ - std::string generateSubscriptionId(); - - /** - * @brief Generates a message requesting a relay to close the subscription with the given ID. - * @returns A stringified JSON object representing the close request. - */ - std::string generateCloseRequest(std::string subscriptionId); - - /** - * @brief Indicates whether the the service has an open subscription with the given ID. - * @returns True if the service has the subscription, false otherwise. - */ - bool hasSubscription(std::string subscriptionId); - - /** - * @brief Indicates whether the service has an open subscription with the given ID on the given - * relay. - * @returns True if the subscription exists on the relay, false otherwise. - */ - bool hasSubscription(std::string subscriptionId, std::string relay); - - /** - * @brief Parses EVENT messages received from the relay and invokes the given event handler. - * @param message The raw message received from the relay. - * @param eventHandler A callable object that will be invoked with the subscription ID and the - * payload of the event. - * @param eoseHandler A callable object that will be invoked with the subscription ID when the - * relay sends an EOSE message, indicating it has reached the end of stored events for the - * given query. - * @param closeHandler A callable object that will be invoked with the subscription ID and the - * message sent by the relay if the subscription is ended by the relay. - */ - void onSubscriptionMessage( - std::string message, - std::function<void(const std::string&, std::shared_ptr<data::Event>)> eventHandler, - std::function<void(const std::string&)> eoseHandler, - std::function<void(const std::string&, const std::string&)> closeHandler); - - /** - * @brief Parses OK messages received from the relay and invokes the given acceptance handler. - * @remark The OK message type is sent to indicate whether the relay has accepted an event sent - * by the client. Note that this is distinct from whether the message was successfully sent to - * the relay over the WebSocket connection. - */ - void onAcceptance(std::string message, std::function<void(const bool)> acceptanceHandler); + virtual std::vector<std::string> closeSubscriptions() = 0; }; } // namespace nostr diff --git a/include/nostr_service_base.hpp b/include/nostr_service_base.hpp new file mode 100644 index 0000000..6abfedc --- /dev/null +++ b/include/nostr_service_base.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include "nostr.hpp" + +namespace nostr +{ +class NostrServiceBase : public INostrServiceBase +{ +public: + NostrServiceBase( + std::shared_ptr<plog::IAppender> appender, + std::shared_ptr<client::IWebSocketClient> client); + + NostrServiceBase( + std::shared_ptr<plog::IAppender> appender, + std::shared_ptr<client::IWebSocketClient> client, + std::vector<std::string> relays); + + ~NostrServiceBase() override; + + std::vector<std::string> defaultRelays() const; + + std::vector<std::string> activeRelays() const; + + std::unordered_map<std::string, std::vector<std::string>> subscriptions() const; + + std::vector<std::string> openRelayConnections() override; + + std::vector<std::string> openRelayConnections(std::vector<std::string> relays) override; + + void closeRelayConnections() override; + + void closeRelayConnections(std::vector<std::string> relays) override; + + // TODO: Make this method return a promise. + std::tuple<std::vector<std::string>, std::vector<std::string>> publishEvent( + std::shared_ptr<data::Event> event) override; + + // TODO: Make this method return a promise. + // TODO: Add a timeout to this method to prevent hanging while waiting for the relay. + std::vector<std::shared_ptr<data::Event>> queryRelays( + std::shared_ptr<data::Filters> filters) override; + + std::string queryRelays( + std::shared_ptr<data::Filters> filters, + std::function<void(const std::string&, std::shared_ptr<data::Event>)> eventHandler, + std::function<void(const std::string&)> eoseHandler, + std::function<void(const std::string&, const std::string&)> closeHandler) override; + + std::tuple<std::vector<std::string>, std::vector<std::string>> closeSubscription( + std::string subscriptionId) override; + + bool closeSubscription(std::string subscriptionId, std::string relay) override; + + std::vector<std::string> closeSubscriptions() override; + +private: + ///< The maximum number of events the service will store for each subscription. + const int MAX_EVENTS_PER_SUBSCRIPTION = 128; + + ///< The WebSocket client used to communicate with relays. + std::shared_ptr<client::IWebSocketClient> _client; + + ///< A mutex to protect the instance properties. + std::mutex _propertyMutex; + + ///< The default set of Nostr relays to which the service will attempt to connect. + std::vector<std::string> _defaultRelays; + + ///< The set of Nostr relays to which the service is currently connected. + std::vector<std::string> _activeRelays; + + ///< A map from subscription IDs to the relays on which each subscription is open. + std::unordered_map<std::string, std::vector<std::string>> _subscriptions; + + std::vector<std::string> _getConnectedRelays(std::vector<std::string> relays); + + std::vector<std::string> _getUnconnectedRelays(std::vector<std::string> relays); + + bool _isConnected(std::string relay); + + void _eraseActiveRelay(std::string relay); + + void _connect(std::string relay); + + void _disconnect(std::string relay); + + std::string _generateSubscriptionId(); + + std::string _generateCloseRequest(std::string subscriptionId); + + bool _hasSubscription(std::string subscriptionId); + + bool _hasSubscription(std::string subscriptionId, std::string relay); + + void _onSubscriptionMessage( + std::string message, + std::function<void(const std::string&, std::shared_ptr<data::Event>)> eventHandler, + std::function<void(const std::string&)> eoseHandler, + std::function<void(const std::string&, const std::string&)> closeHandler); + + void _onAcceptance(std::string message, std::function<void(const bool)> acceptanceHandler); +}; +} // namespace nostr diff --git a/include/signer/noscrypt_signer.hpp b/include/signer/noscrypt_signer.hpp new file mode 100644 index 0000000..1476303 --- /dev/null +++ b/include/signer/noscrypt_signer.hpp @@ -0,0 +1,55 @@ +#pragma once + +extern "C" +{ +#include <noscrypt.h> +} + +#include "signer.hpp" + +namespace nostr +{ +namespace signer +{ +class NoscryptSigner : public INostrConnectSigner +{ +public: + NoscryptSigner(std::shared_ptr<plog::IAppender> appender); + + ~NoscryptSigner(); + + void receiveConnection(std::string connectionToken) override; + + void initiateConnection( + std::string relay, + std::string name, + std::string url, + std::string description) override; + + void sign(std::shared_ptr<data::Event> event) override; + +private: + std::shared_ptr<NCContext> noscryptContext; + + std::string localPrivateKey; + std::string localPublicKey; + + /** + * @brief Initializes the noscrypt library context into the class's `context` property. + * @returns `true` if successful, `false` otherwise. + */ + std::shared_ptr<NCContext> _initNoscryptContext(); + + void _logNoscryptResult(NCResult result); + + /** + * @brief Generates a private/public key pair for local use. + * @returns The generated keypair of the form `[privateKey, publicKey]`, or a pair of empty + * strings if the function failed. + * @remarks This keypair is intended for temporary use, and should not be saved or used outside + * of this class. + */ + std::tuple<std::string, std::string> _createLocalKeypair(); +}; +} // namespace signer +} // namespace nostr diff --git a/include/signer/signer.hpp b/include/signer/signer.hpp index cc4fd03..e16aa38 100644 --- a/include/signer/signer.hpp +++ b/include/signer/signer.hpp @@ -14,15 +14,14 @@ namespace nostr { namespace signer { -class ISigner; -class INostrConnectSigner; - /** * @brief An interface for Nostr event signing that implements NIP-46. */ class ISigner { public: + virtual ~ISigner() = default; + /** * @brief Signs the given Nostr event. * @param event The event to sign. @@ -34,6 +33,8 @@ public: class INostrConnectSigner : public ISigner { public: + virtual ~INostrConnectSigner() = default; + virtual void receiveConnection(std::string connectionToken) = 0; virtual void initiateConnection( diff --git a/src/client/websocketpp_client.cpp b/src/client/websocketpp_client.cpp index 9967d74..3cc6c99 100644 --- a/src/client/websocketpp_client.cpp +++ b/src/client/websocketpp_client.cpp @@ -1,132 +1,105 @@ -#include "web_socket_client.hpp" - -using std::error_code; -using std::function; -using std::lock_guard; -using std::make_tuple; -using std::mutex; -using std::string; -using std::tuple; -using std::unordered_map; - -namespace nostr -{ -namespace client -{ -/** - * @brief An implementation of the `IWebSocketClient` interface that uses the WebSocket++ library. - */ -class WebsocketppClient : public IWebSocketClient -{ -public: - void start() override - { - this->_client.init_asio(); - this->_client.start_perpetual(); - }; - - void stop() override - { - this->_client.stop_perpetual(); - this->_client.stop(); - }; +#include "websocketpp_client.hpp" - void openConnection(string uri) override - { - error_code error; - websocketpp_client::connection_ptr connection = this->_client.get_connection(uri, error); - - if (error.value() == -1) - { - // PLOG_ERROR << "Error connecting to relay " << relay << ": " << error.message(); - } +using namespace std; - // Configure the connection here via the connection pointer. - connection->set_fail_handler([this, uri](auto handle) { - // PLOG_ERROR << "Error connecting to relay " << relay << ": Handshake failed."; - lock_guard<mutex> lock(this->_propertyMutex); - if (this->isConnected(uri)) - { - this->_connectionHandles.erase(uri); - } - }); +void nostr::client::WebsocketppClient::start() +{ + this->_client.init_asio(); + this->_client.start_perpetual(); +}; - lock_guard<mutex> lock(this->_propertyMutex); - this->_connectionHandles[uri] = connection->get_handle(); - this->_client.connect(connection); - }; +void nostr::client::WebsocketppClient::stop() +{ + this->_client.stop_perpetual(); + this->_client.stop(); +}; - bool isConnected(string uri) override - { - lock_guard<mutex> lock(this->_propertyMutex); - return this->_connectionHandles.find(uri) != this->_connectionHandles.end(); - }; +void nostr::client::WebsocketppClient::openConnection(string uri) +{ + error_code error; + websocketpp_client::connection_ptr connection = this->_client.get_connection(uri, error); - tuple<string, bool> send(string message, string uri) override + if (error.value() == -1) { - error_code error; + // PLOG_ERROR << "Error connecting to relay " << relay << ": " << error.message(); + } - // Make sure the connection isn't closed from under us. + // Configure the connection here via the connection pointer. + connection->set_fail_handler([this, uri](auto handle) { + // PLOG_ERROR << "Error connecting to relay " << relay << ": Handshake failed."; lock_guard<mutex> lock(this->_propertyMutex); - this->_client.send( - this->_connectionHandles[uri], - message, - websocketpp::frame::opcode::text, - error); - - if (error.value() == -1) + if (this->isConnected(uri)) { - return make_tuple(uri, false); + this->_connectionHandles.erase(uri); } + }); - return make_tuple(uri, true); - }; + lock_guard<mutex> lock(this->_propertyMutex); + this->_connectionHandles[uri] = connection->get_handle(); + this->_client.connect(connection); +}; - tuple<string, bool> send(string message, string uri, function<void(const string&)> messageHandler) override - { - auto successes = this->send(message, uri); - this->receive(uri, messageHandler); - return successes; - }; +bool nostr::client::WebsocketppClient::isConnected(string uri) +{ + lock_guard<mutex> lock(this->_propertyMutex); + return this->_connectionHandles.find(uri) != this->_connectionHandles.end(); +}; - void receive(string uri, function<void(const string&)> messageHandler) override - { - lock_guard<mutex> lock(this->_propertyMutex); - auto connectionHandle = this->_connectionHandles[uri]; - auto connection = this->_client.get_con_from_hdl(connectionHandle); +tuple<string, bool> nostr::client::WebsocketppClient::send(string message, string uri) +{ + error_code error; - connection->set_message_handler([messageHandler]( - websocketpp::connection_hdl connectionHandle, - websocketpp_client::message_ptr message) - { - messageHandler(message->get_payload()); - }); - }; + // Make sure the connection isn't closed from under us. + lock_guard<mutex> lock(this->_propertyMutex); + this->_client.send( + this->_connectionHandles[uri], + message, + websocketpp::frame::opcode::text, + error); - void closeConnection(string uri) override + if (error.value() == -1) { - lock_guard<mutex> lock(this->_propertyMutex); + return make_tuple(uri, false); + } - websocketpp::connection_hdl handle = this->_connectionHandles[uri]; - this->_client.close( - handle, - websocketpp::close::status::going_away, - "_client requested close."); - - this->_connectionHandles.erase(uri); - }; + return make_tuple(uri, true); +}; -private: - typedef websocketpp::client<websocketpp::config::asio_client> websocketpp_client; - typedef unordered_map<string, websocketpp::connection_hdl>::iterator connection_hdl_iterator; +tuple<string, bool> nostr::client::WebsocketppClient::send( + string message, + string uri, + function<void(const string&)> messageHandler) +{ + auto successes = this->send(message, uri); + this->receive(uri, messageHandler); + return successes; +}; - websocketpp_client _client; - unordered_map<string, websocketpp::connection_hdl> _connectionHandles; - mutex _propertyMutex; +void nostr::client::WebsocketppClient::receive( + string uri, + function<void(const string&)> messageHandler) +{ + lock_guard<mutex> lock(this->_propertyMutex); + auto connectionHandle = this->_connectionHandles[uri]; + auto connection = this->_client.get_con_from_hdl(connectionHandle); - void onMessage(websocketpp::connection_hdl handle, websocketpp_client::message_ptr message) + connection->set_message_handler([messageHandler]( + websocketpp::connection_hdl connectionHandle, + websocketpp_client::message_ptr message) { - }; + messageHandler(message->get_payload()); + }); +}; + +void nostr::client::WebsocketppClient::closeConnection(string uri) +{ + lock_guard<mutex> lock(this->_propertyMutex); + + websocketpp::connection_hdl handle = this->_connectionHandles[uri]; + this->_client.close( + handle, + websocketpp::close::status::going_away, + "_client requested close."); + + this->_connectionHandles.erase(uri); }; -} // namespace client -} // namespace nostr diff --git a/src/data/event.cpp b/src/data/event.cpp index 620ee3f..5f611ff 100644 --- a/src/data/event.cpp +++ b/src/data/event.cpp @@ -88,12 +88,6 @@ void Event::validate() { throw std::invalid_argument("Event::validate: A valid event kind is required."); } - - bool hasSignature = this->sig.length() > 0; - if (!hasSignature) - { - throw std::invalid_argument("Event::validate: The event must be signed."); - } }; string Event::generateId(string serializedData) const diff --git a/src/nostr_service.cpp b/src/nostr_service_base.cpp index 9f6b8ce..bce6728 100644 --- a/src/nostr_service.cpp +++ b/src/nostr_service_base.cpp @@ -1,53 +1,52 @@ -#include "nostr.hpp" +#include "nostr_service_base.hpp" using namespace nlohmann; using namespace std; -namespace nostr -{ -NostrService::NostrService( +nostr::NostrServiceBase::NostrServiceBase( shared_ptr<plog::IAppender> appender, - shared_ptr<client::IWebSocketClient> client, - shared_ptr<signer::ISigner> signer) -: NostrService(appender, client, signer, {}) { }; + shared_ptr<client::IWebSocketClient> client) +: NostrServiceBase(appender, client, {}) { }; -NostrService::NostrService( +nostr::NostrServiceBase::NostrServiceBase( shared_ptr<plog::IAppender> appender, shared_ptr<client::IWebSocketClient> client, - shared_ptr<signer::ISigner> signer, vector<string> relays) -: _defaultRelays(relays), _client(client), _signer(signer) +: _defaultRelays(relays), _client(client) { plog::init(plog::debug, appender.get()); client->start(); }; -NostrService::~NostrService() +nostr::NostrServiceBase::~NostrServiceBase() { this->_client->stop(); }; -vector<string> NostrService::defaultRelays() const { return this->_defaultRelays; }; +vector<string> nostr::NostrServiceBase::defaultRelays() const +{ return this->_defaultRelays; }; -vector<string> NostrService::activeRelays() const { return this->_activeRelays; }; +vector<string> nostr::NostrServiceBase::activeRelays() const +{ return this->_activeRelays; }; -unordered_map<string, vector<string>> NostrService::subscriptions() const { return this->_subscriptions; }; +unordered_map<string, vector<string>> nostr::NostrServiceBase::subscriptions() const +{ return this->_subscriptions; }; -vector<string> NostrService::openRelayConnections() +vector<string> nostr::NostrServiceBase::openRelayConnections() { return this->openRelayConnections(this->_defaultRelays); }; -vector<string> NostrService::openRelayConnections(vector<string> relays) +vector<string> nostr::NostrServiceBase::openRelayConnections(vector<string> relays) { PLOG_INFO << "Attempting to connect to Nostr relays."; - vector<string> unconnectedRelays = this->getUnconnectedRelays(relays); + vector<string> unconnectedRelays = this->_getUnconnectedRelays(relays); vector<thread> connectionThreads; for (string relay : unconnectedRelays) { thread connectionThread([this, relay]() { - this->connect(relay); + this->_connect(relay); }); connectionThreads.push_back(move(connectionThread)); } @@ -65,7 +64,7 @@ vector<string> NostrService::openRelayConnections(vector<string> relays) return this->_activeRelays; }; -void NostrService::closeRelayConnections() +void nostr::NostrServiceBase::closeRelayConnections() { if (this->_activeRelays.size() == 0) { @@ -76,16 +75,16 @@ void NostrService::closeRelayConnections() this->closeRelayConnections(this->_activeRelays); }; -void NostrService::closeRelayConnections(vector<string> relays) +void nostr::NostrServiceBase::closeRelayConnections(vector<string> relays) { PLOG_INFO << "Disconnecting from Nostr relays."; - vector<string> connectedRelays = getConnectedRelays(relays); + vector<string> connectedRelays = this->_getConnectedRelays(relays); vector<thread> disconnectionThreads; for (string relay : connectedRelays) { thread disconnectionThread([this, relay]() { - this->disconnect(relay); + this->_disconnect(relay); }); disconnectionThreads.push_back(move(disconnectionThread)); @@ -101,7 +100,8 @@ void NostrService::closeRelayConnections(vector<string> relays) }; // TODO: Make this method return a promise. -tuple<vector<string>, vector<string>> NostrService::publishEvent(shared_ptr<data::Event> event) +tuple<vector<string>, vector<string>> nostr::NostrServiceBase::publishEvent( + shared_ptr<nostr::data::Event> event) { vector<string> successfulRelays; vector<string> failedRelays; @@ -111,7 +111,6 @@ tuple<vector<string>, vector<string>> NostrService::publishEvent(shared_ptr<data json message; try { - this->_signer->sign(event); message = json::array({ "EVENT", event->serialize() }); } catch (const std::invalid_argument& e) @@ -138,7 +137,7 @@ tuple<vector<string>, vector<string>> NostrService::publishEvent(shared_ptr<data relay, [this, &relay, &event, &publishPromise](string response) { - this->onAcceptance(response, [this, &relay, &event, &publishPromise](bool isAccepted) + this->_onAcceptance(response, [this, &relay, &event, &publishPromise](bool isAccepted) { if (isAccepted) { @@ -182,7 +181,8 @@ tuple<vector<string>, vector<string>> NostrService::publishEvent(shared_ptr<data // TODO: Make this method return a promise. // TODO: Add a timeout to this method to prevent hanging while waiting for the relay. -vector<shared_ptr<data::Event>> NostrService::queryRelays(shared_ptr<data::Filters> filters) +vector<shared_ptr<nostr::data::Event>> nostr::NostrServiceBase::queryRelays( + shared_ptr<nostr::data::Filters> filters) { if (filters->limit > 64 || filters->limit < 1) { @@ -190,9 +190,9 @@ vector<shared_ptr<data::Event>> NostrService::queryRelays(shared_ptr<data::Filte filters->limit = 16; } - vector<shared_ptr<data::Event>> events; + vector<shared_ptr<nostr::data::Event>> events; - string subscriptionId = this->generateSubscriptionId(); + string subscriptionId = this->_generateSubscriptionId(); string request; try @@ -226,9 +226,9 @@ vector<shared_ptr<data::Event>> NostrService::queryRelays(shared_ptr<data::Filte relay, [this, &relay, &events, &eosePromise](string payload) { - this->onSubscriptionMessage( + this->_onSubscriptionMessage( payload, - [&events](const string&, shared_ptr<data::Event> event) + [&events](const string&, shared_ptr<nostr::data::Event> event) { events.push_back(event); }, @@ -276,16 +276,16 @@ vector<shared_ptr<data::Event>> NostrService::queryRelays(shared_ptr<data::Filte return events; }; -string NostrService::queryRelays( - shared_ptr<data::Filters> filters, - function<void(const string&, shared_ptr<data::Event>)> eventHandler, +string nostr::NostrServiceBase::queryRelays( + shared_ptr<nostr::data::Filters> filters, + function<void(const string&, shared_ptr<nostr::data::Event>)> eventHandler, function<void(const string&)> eoseHandler, function<void(const string&, const string&)> closeHandler) { vector<string> successfulRelays; vector<string> failedRelays; - string subscriptionId = this->generateSubscriptionId(); + string subscriptionId = this->_generateSubscriptionId(); string request = filters->serialize(subscriptionId); vector<future<tuple<string, bool>>> requestFutures; for (const string relay : this->_activeRelays) @@ -302,7 +302,7 @@ string NostrService::queryRelays( relay, [this, &eventHandler, &eoseHandler, &closeHandler](string payload) { - this->onSubscriptionMessage(payload, eventHandler, eoseHandler, closeHandler); + this->_onSubscriptionMessage(payload, eventHandler, eoseHandler, closeHandler); }); }); requestFutures.push_back(move(requestFuture)); @@ -328,7 +328,7 @@ string NostrService::queryRelays( return subscriptionId; }; -tuple<vector<string>, vector<string>> NostrService::closeSubscription(string subscriptionId) +tuple<vector<string>, vector<string>> nostr::NostrServiceBase::closeSubscription(string subscriptionId) { vector<string> successfulRelays; vector<string> failedRelays; @@ -388,21 +388,21 @@ tuple<vector<string>, vector<string>> NostrService::closeSubscription(string sub return make_tuple(successfulRelays, failedRelays); }; -bool NostrService::closeSubscription(string subscriptionId, string relay) +bool nostr::NostrServiceBase::closeSubscription(string subscriptionId, string relay) { - if (!this->hasSubscription(subscriptionId, relay)) + if (!this->_hasSubscription(subscriptionId, relay)) { PLOG_WARNING << "Subscription " << subscriptionId << " not found on relay " << relay; return false; } - if (!this->isConnected(relay)) + if (!this->_isConnected(relay)) { PLOG_WARNING << "Relay " << relay << " is not connected."; return false; } - string request = this->generateCloseRequest(subscriptionId); + string request = this->_generateCloseRequest(subscriptionId); auto [uri, success] = this->_client->send(request, relay); if (success) @@ -428,7 +428,7 @@ bool NostrService::closeSubscription(string subscriptionId, string relay) return success; }; -vector<string> NostrService::closeSubscriptions() +vector<string> nostr::NostrServiceBase::closeSubscriptions() { unique_lock<mutex> lock(this->_propertyMutex); vector<string> subscriptionIds; @@ -451,7 +451,7 @@ vector<string> NostrService::closeSubscriptions() return remainingSubscriptions; }; -vector<string> NostrService::getConnectedRelays(vector<string> relays) +vector<string> nostr::NostrServiceBase::_getConnectedRelays(vector<string> relays) { PLOG_VERBOSE << "Identifying connected relays."; vector<string> connectedRelays; @@ -468,7 +468,7 @@ vector<string> NostrService::getConnectedRelays(vector<string> relays) } else if (isActive && !isConnected) { - this->eraseActiveRelay(relay); + this->_eraseActiveRelay(relay); } else if (!isActive && isConnected) { @@ -479,7 +479,7 @@ vector<string> NostrService::getConnectedRelays(vector<string> relays) return connectedRelays; }; -vector<string> NostrService::getUnconnectedRelays(vector<string> relays) +vector<string> nostr::NostrServiceBase::_getUnconnectedRelays(vector<string> relays) { PLOG_VERBOSE << "Identifying unconnected relays."; vector<string> unconnectedRelays; @@ -498,7 +498,7 @@ vector<string> NostrService::getUnconnectedRelays(vector<string> relays) else if (isActive && !isConnected) { PLOG_VERBOSE << "Relay " << relay << " is active but not connected. Removing from active relays list."; - this->eraseActiveRelay(relay); + this->_eraseActiveRelay(relay); unconnectedRelays.push_back(relay); } else if (!isActive && isConnected) @@ -510,7 +510,7 @@ vector<string> NostrService::getUnconnectedRelays(vector<string> relays) return unconnectedRelays; }; -bool NostrService::isConnected(string relay) +bool nostr::NostrServiceBase::_isConnected(string relay) { auto it = find(this->_activeRelays.begin(), this->_activeRelays.end(), relay); if (it != this->_activeRelays.end()) // If the relay is in this->_activeRelays @@ -520,7 +520,7 @@ bool NostrService::isConnected(string relay) return false; }; -void NostrService::eraseActiveRelay(string relay) +void nostr::NostrServiceBase::_eraseActiveRelay(string relay) { auto it = find(this->_activeRelays.begin(), this->_activeRelays.end(), relay); if (it != this->_activeRelays.end()) // If the relay is in this->_activeRelays @@ -529,7 +529,7 @@ void NostrService::eraseActiveRelay(string relay) } }; -void NostrService::connect(string relay) +void nostr::NostrServiceBase::_connect(string relay) { PLOG_VERBOSE << "Connecting to relay " << relay; this->_client->openConnection(relay); @@ -548,28 +548,28 @@ void NostrService::connect(string relay) } }; -void NostrService::disconnect(string relay) +void nostr::NostrServiceBase::_disconnect(string relay) { this->_client->closeConnection(relay); lock_guard<mutex> lock(this->_propertyMutex); - this->eraseActiveRelay(relay); + this->_eraseActiveRelay(relay); }; -string NostrService::generateSubscriptionId() +string nostr::NostrServiceBase::_generateSubscriptionId() { UUIDv4::UUIDGenerator<std::mt19937_64> uuidGenerator; UUIDv4::UUID uuid = uuidGenerator.getUUID(); return uuid.str(); }; -string NostrService::generateCloseRequest(string subscriptionId) +string nostr::NostrServiceBase::_generateCloseRequest(string subscriptionId) { json jarr = json::array({ "CLOSE", subscriptionId }); return jarr.dump(); }; -bool NostrService::hasSubscription(string subscriptionId) +bool nostr::NostrServiceBase::_hasSubscription(string subscriptionId) { lock_guard<mutex> lock(this->_propertyMutex); auto it = this->_subscriptions.find(subscriptionId); @@ -577,7 +577,7 @@ bool NostrService::hasSubscription(string subscriptionId) return it != this->_subscriptions.end(); }; -bool NostrService::hasSubscription(string subscriptionId, string relay) +bool nostr::NostrServiceBase::_hasSubscription(string subscriptionId, string relay) { lock_guard<mutex> lock(this->_propertyMutex); auto subscriptionIt = this->_subscriptions.find(subscriptionId); @@ -593,9 +593,9 @@ bool NostrService::hasSubscription(string subscriptionId, string relay) return relayIt != relays.end(); }; -void NostrService::onSubscriptionMessage( +void nostr::NostrServiceBase::_onSubscriptionMessage( string message, - function<void(const string&, shared_ptr<data::Event>)> eventHandler, + function<void(const string&, shared_ptr<nostr::data::Event>)> eventHandler, function<void(const string&)> eoseHandler, function<void(const string&, const string&)> closeHandler) { @@ -606,8 +606,8 @@ void NostrService::onSubscriptionMessage( if (messageType == "EVENT") { string subscriptionId = jMessage.at(1); - data::Event event = data::Event::fromString(jMessage.at(2)); - eventHandler(subscriptionId, make_shared<data::Event>(event)); + nostr::data::Event event = nostr::data::Event::fromString(jMessage.at(2)); + eventHandler(subscriptionId, make_shared<nostr::data::Event>(event)); } else if (messageType == "EOSE") { @@ -638,7 +638,7 @@ void NostrService::onSubscriptionMessage( } }; -void NostrService::onAcceptance(string message, function<void(const bool)> acceptanceHandler) +void nostr::NostrServiceBase::_onAcceptance(string message, function<void(const bool)> acceptanceHandler) { try { @@ -656,4 +656,3 @@ void NostrService::onAcceptance(string message, function<void(const bool)> accep throw je; } }; -} // namespace nostr diff --git a/src/signer/noscrypt_signer.cpp b/src/signer/noscrypt_signer.cpp index c56070c..39bb667 100644 --- a/src/signer/noscrypt_signer.cpp +++ b/src/signer/noscrypt_signer.cpp @@ -1,169 +1,151 @@ -#include <noscrypt.h> - -#include "signer.hpp" +#include "noscrypt_signer.hpp" using namespace std; -namespace nostr +nostr::signer::NoscryptSigner::NoscryptSigner(shared_ptr<plog::IAppender> appender) { -namespace signer + plog::init(plog::debug, appender.get()); + + this->noscryptContext = this->_initNoscryptContext(); + if (this->noscryptContext == nullptr) + { + return; + } + + const auto [privateKey, publicKey] = this->_createLocalKeypair(); + this->localPrivateKey = privateKey; + this->localPublicKey = publicKey; +}; + +nostr::signer::NoscryptSigner::~NoscryptSigner() { -class NoscryptSigner : public INostrConnectSigner + NCDestroyContext(this->noscryptContext.get()); +}; + +void nostr::signer::NoscryptSigner::receiveConnection(string connectionToken) { -public: - NoscryptSigner(shared_ptr<plog::IAppender> appender) - { - plog::init(plog::debug, appender.get()); + // Receive the connection token here. +}; - this->noscryptContext = this->initNoscryptContext(); - if (this->noscryptContext == nullptr) - { - return; - } +void nostr::signer::NoscryptSigner::initiateConnection( + string relay, + string name, + string url, + string description) +{ + // Initiate the connection here. +}; - const auto [privateKey, publicKey] = this->createLocalKeypair(); - this->localPrivateKey = privateKey; - this->localPublicKey = publicKey; - }; +void nostr::signer::NoscryptSigner::sign(shared_ptr<data::Event> event) +{ + // Sign the event here. +}; - ~NoscryptSigner() - { - NCDestroyContext(this->noscryptContext.get()); - }; +/** + * @brief Initializes the noscrypt library context into the class's `context` property. + * @returns `true` if successful, `false` otherwise. + */ +shared_ptr<NCContext> nostr::signer::NoscryptSigner::_initNoscryptContext() +{ + shared_ptr<NCContext> context(new NCContext); + auto contextStructSize = NCGetContextStructSize(); + unique_ptr<uint8_t> randomEntropy(new uint8_t[contextStructSize]); - void receiveConnection(string connectionToken) override - { - // Receive the connection token here. - }; - - void initiateConnection( - string relay, - string name, - string url, - string description) override - { - // Initiate the connection here. - }; + random_device rd; + mt19937 gen(rd()); + uniform_int_distribution<> dist(0, contextStructSize); + generate_n(randomEntropy.get(), contextStructSize, [&]() { return dist(gen); }); - void sign(shared_ptr<data::Event> event) override + NCResult result = NCInitContext(context.get(), randomEntropy.get()); + this->_logNoscryptResult(result); + + if (result != NC_SUCCESS) { - // Sign the event here. - }; + return nullptr; + } -private: - shared_ptr<NCContext> noscryptContext; + return context; +}; - string localPrivateKey; - string localPublicKey; +void nostr::signer::NoscryptSigner::_logNoscryptResult(NCResult result) +{ + switch (result) { + case NC_SUCCESS: + PLOG_INFO << "noscrypt - success"; + break; - /** - * @brief Initializes the noscrypt library context into the class's `context` property. - * @returns `true` if successful, `false` otherwise. - */ - shared_ptr<NCContext> initNoscryptContext() - { - shared_ptr<NCContext> context(new NCContext); - auto contextStructSize = NCGetContextStructSize(); - unique_ptr<uint8_t> randomEntropy(new uint8_t[contextStructSize]); + case E_NULL_PTR: + PLOG_ERROR << "noscrypt - error: A null pointer was passed to the initializer."; + break; - random_device rd; - mt19937 gen(rd()); - uniform_int_distribution<> dist(0, contextStructSize); - generate_n(randomEntropy.get(), contextStructSize, [&]() { return dist(gen); }); + case E_INVALID_ARG: + PLOG_ERROR << "noscrypt - error: An invalid argument was passed to the initializer."; + break; + + case E_INVALID_CONTEXT: + PLOG_ERROR << "noscrypt - error: The NCContext struct is in an invalid state."; + break; + + case E_ARGUMENT_OUT_OF_RANGE: + PLOG_ERROR << "noscrypt - error: An initializer argument was outside the range of acceptable values."; + break; + + case E_OPERATION_FAILED: + PLOG_ERROR << "noscrypt - error"; + break; + } +}; - NCResult result = NCInitContext(context.get(), randomEntropy.get()); - this->logNoscryptResult(result); +/** + * @brief Generates a private/public key pair for local use. + * @returns The generated keypair of the form `[privateKey, publicKey]`, or a pair of empty + * strings if the function failed. + * @remarks This keypair is intended for temporary use, and should not be saved or used outside + * of this class. + */ +tuple<string, string> nostr::signer::NoscryptSigner::_createLocalKeypair() +{ + string privateKey; + string publicKey; - if (result != NC_SUCCESS) - { - return nullptr; - } + // To generate a private key, all we need is a random 32-bit buffer. + unique_ptr<NCSecretKey> secretKey(new NCSecretKey); - return context; - }; + random_device rd; + mt19937 gen(rd()); + uniform_int_distribution<> dist(0, NC_SEC_KEY_SIZE); + generate_n(secretKey.get()->key, NC_SEC_KEY_SIZE, [&]() { return dist(gen); }); - void logNoscryptResult(NCResult result) + // Convert the buffer into a hex string for a more human-friendly representation. + stringstream secretKeyStream; + for (int i = 0; i < NC_SEC_KEY_SIZE; i++) + { + secretKeyStream << hex << setw(2) << setfill('0') << static_cast<int>(secretKey->key[i]); + } + privateKey = secretKeyStream.str(); + + // Use noscrypt to derive the public key from its private counterpart. + unique_ptr<NCPublicKey> pubkey(new NCPublicKey); + NCResult result = NCGetPublicKey( + this->noscryptContext.get(), + secretKey.get(), + pubkey.get()); + this->_logNoscryptResult(result); + + if (result != NC_SUCCESS) { - switch (result) { - case NC_SUCCESS: - PLOG_INFO << "noscrypt - success"; - break; - - case E_NULL_PTR: - PLOG_ERROR << "noscrypt - error: A null pointer was passed to the initializer."; - break; - - case E_INVALID_ARG: - PLOG_ERROR << "noscrypt - error: An invalid argument was passed to the initializer."; - break; - - case E_INVALID_CONTEXT: - PLOG_ERROR << "noscrypt - error: The NCContext struct is in an invalid state."; - break; - - case E_ARGUMENT_OUT_OF_RANGE: - PLOG_ERROR << "noscrypt - error: An initializer argument was outside the range of acceptable values."; - break; - - case E_OPERATION_FAILED: - PLOG_ERROR << "noscrypt - error"; - break; - } - }; - - /** - * @brief Generates a private/public key pair for local use. - * @returns The generated keypair of the form `[privateKey, publicKey]`, or a pair of empty - * strings if the function failed. - * @remarks This keypair is intended for temporary use, and should not be saved or used outside - * of this class. - */ - tuple<string, string> createLocalKeypair() + // Return empty strings if the key generation fails. + return make_tuple(string(), string()); + } + + // Convert the now-populated pubkey buffer into a hex string for the pubkey representation + // used by Nostr events. + stringstream pubkeyStream; + for (int i = 0; i < NC_SEC_KEY_SIZE; i++) { - string privateKey; - string publicKey; - - // To generate a private key, all we need is a random 32-bit buffer. - unique_ptr<NCSecretKey> secretKey(new NCSecretKey); - - random_device rd; - mt19937 gen(rd()); - uniform_int_distribution<> dist(0, NC_SEC_KEY_SIZE); - generate_n(secretKey.get()->key, NC_SEC_KEY_SIZE, [&]() { return dist(gen); }); - - // Convert the buffer into a hex string for a more human-friendly representation. - stringstream secretKeyStream; - for (int i = 0; i < NC_SEC_KEY_SIZE; i++) - { - secretKeyStream << hex << setw(2) << setfill('0') << static_cast<int>(secretKey->key[i]); - } - privateKey = secretKeyStream.str(); - - // Use noscrypt to derive the public key from its private counterpart. - unique_ptr<NCPublicKey> pubkey(new NCPublicKey); - NCResult result = NCGetPublicKey( - this->noscryptContext.get(), - secretKey.get(), - pubkey.get()); - this->logNoscryptResult(result); - - if (result != NC_SUCCESS) - { - // Return empty strings if the key generation fails. - return make_tuple(string(), string()); - } - - // Convert the now-populated pubkey buffer into a hex string for the pubkey representation - // used by Nostr events. - stringstream pubkeyStream; - for (int i = 0; i < NC_SEC_KEY_SIZE; i++) - { - pubkeyStream << hex << setw(2) << setfill('0') << static_cast<int>(pubkey->key[i]); - } - publicKey = pubkeyStream.str(); - - return make_tuple(privateKey, publicKey); - }; + pubkeyStream << hex << setw(2) << setfill('0') << static_cast<int>(pubkey->key[i]); + } + publicKey = pubkeyStream.str(); + + return make_tuple(privateKey, publicKey); }; -} // namespace signer -} // namespace nostr diff --git a/test/nostr_service_test.cpp b/test/nostr_service_base_test.cpp index e5375db..9116816 100644 --- a/test/nostr_service_test.cpp +++ b/test/nostr_service_base_test.cpp @@ -9,8 +9,8 @@ #include <websocketpp/client.hpp> #include "nostr.hpp" +#include "nostr_service_base.hpp" #include "client/web_socket_client.hpp" -#include "signer/signer.hpp" using namespace nostr; using namespace std; @@ -32,16 +32,7 @@ public: MOCK_METHOD(void, closeConnection, (string uri), (override)); }; -class FakeSigner : public signer::ISigner -{ -public: - void sign(shared_ptr<nostr::data::Event> event) override - { - event->sig = "fake_signature"; - }; -}; - -class NostrServiceTest : public testing::Test +class NostrServiceBaseTest : public testing::Test { public: inline static const vector<string> defaultTestRelays = @@ -127,10 +118,7 @@ public: } static const string getTestEventMessage(shared_ptr<nostr::data::Event> event, string subscriptionId) - { - auto signer = make_unique<FakeSigner>(); - signer->sign(event); - + { json jarr = json::array(); jarr.push_back("EVENT"); jarr.push_back(subscriptionId); @@ -170,26 +158,24 @@ public: protected: shared_ptr<plog::ConsoleAppender<plog::TxtFormatter>> testAppender; shared_ptr<MockWebSocketClient> mockClient; - shared_ptr<FakeSigner> fakeSigner; void SetUp() override { testAppender = make_shared<plog::ConsoleAppender<plog::TxtFormatter>>(); mockClient = make_shared<MockWebSocketClient>(); - fakeSigner = make_shared<FakeSigner>(); }; }; -TEST_F(NostrServiceTest, Constructor_StartsClient) +TEST_F(NostrServiceBaseTest, Constructor_StartsClient) { EXPECT_CALL(*mockClient, start()).Times(1); - auto nostrService = make_unique<nostr::NostrService>(testAppender, mockClient, fakeSigner); + auto nostrService = make_unique<nostr::NostrServiceBase>(testAppender, mockClient); }; -TEST_F(NostrServiceTest, Constructor_InitializesService_WithNoDefaultRelays) +TEST_F(NostrServiceBaseTest, Constructor_InitializesService_WithNoDefaultRelays) { - auto nostrService = make_unique<nostr::NostrService>(testAppender, mockClient, fakeSigner); + auto nostrService = make_unique<nostr::NostrServiceBase>(testAppender, mockClient); auto defaultRelays = nostrService->defaultRelays(); auto activeRelays = nostrService->activeRelays(); @@ -197,12 +183,11 @@ TEST_F(NostrServiceTest, Constructor_InitializesService_WithNoDefaultRelays) ASSERT_EQ(activeRelays.size(), 0); }; -TEST_F(NostrServiceTest, Constructor_InitializesService_WithProvidedDefaultRelays) +TEST_F(NostrServiceBaseTest, Constructor_InitializesService_WithProvidedDefaultRelays) { - auto nostrService = make_unique<nostr::NostrService>( + auto nostrService = make_unique<nostr::NostrServiceBase>( testAppender, mockClient, - fakeSigner, defaultTestRelays); auto defaultRelays = nostrService->defaultRelays(); auto activeRelays = nostrService->activeRelays(); @@ -215,14 +200,16 @@ TEST_F(NostrServiceTest, Constructor_InitializesService_WithProvidedDefaultRelay ASSERT_EQ(activeRelays.size(), 0); }; -TEST_F(NostrServiceTest, Destructor_StopsClient) +TEST_F(NostrServiceBaseTest, Destructor_StopsClient) { EXPECT_CALL(*mockClient, start()).Times(1); - auto nostrService = make_unique<nostr::NostrService>(testAppender, mockClient, fakeSigner); + auto nostrService = make_unique<nostr::NostrServiceBase>( + testAppender, + mockClient); }; -TEST_F(NostrServiceTest, OpenRelayConnections_OpensConnections_ToDefaultRelays) +TEST_F(NostrServiceBaseTest, OpenRelayConnections_OpensConnections_ToDefaultRelays) { mutex connectionStatusMutex; auto connectionStatus = make_shared<unordered_map<string, bool>>(); @@ -244,10 +231,9 @@ TEST_F(NostrServiceTest, OpenRelayConnections_OpensConnections_ToDefaultRelays) return status; })); - auto nostrService = make_unique<nostr::NostrService>( + auto nostrService = make_unique<nostr::NostrServiceBase>( testAppender, mockClient, - fakeSigner, defaultTestRelays); nostrService->openRelayConnections(); @@ -259,7 +245,7 @@ TEST_F(NostrServiceTest, OpenRelayConnections_OpensConnections_ToDefaultRelays) } }; -TEST_F(NostrServiceTest, OpenRelayConnections_OpensConnections_ToProvidedRelays) +TEST_F(NostrServiceBaseTest, OpenRelayConnections_OpensConnections_ToProvidedRelays) { vector<string> testRelays = { "wss://nos.lol" }; @@ -283,10 +269,9 @@ TEST_F(NostrServiceTest, OpenRelayConnections_OpensConnections_ToProvidedRelays) return status; })); - auto nostrService = make_unique<nostr::NostrService>( + auto nostrService = make_unique<nostr::NostrServiceBase>( testAppender, mockClient, - fakeSigner, defaultTestRelays); nostrService->openRelayConnections(testRelays); @@ -298,7 +283,7 @@ TEST_F(NostrServiceTest, OpenRelayConnections_OpensConnections_ToProvidedRelays) } }; -TEST_F(NostrServiceTest, OpenRelayConnections_AddsOpenConnections_ToActiveRelays) +TEST_F(NostrServiceBaseTest, OpenRelayConnections_AddsOpenConnections_ToActiveRelays) { vector<string> testRelays = { "wss://nos.lol" }; @@ -324,10 +309,9 @@ TEST_F(NostrServiceTest, OpenRelayConnections_AddsOpenConnections_ToActiveRelays return status; })); - auto nostrService = make_unique<nostr::NostrService>( + auto nostrService = make_unique<nostr::NostrServiceBase>( testAppender, mockClient, - fakeSigner, defaultTestRelays); nostrService->openRelayConnections(); @@ -352,7 +336,7 @@ TEST_F(NostrServiceTest, OpenRelayConnections_AddsOpenConnections_ToActiveRelays } }; -TEST_F(NostrServiceTest, CloseRelayConnections_ClosesConnections_ToActiveRelays) +TEST_F(NostrServiceBaseTest, CloseRelayConnections_ClosesConnections_ToActiveRelays) { mutex connectionStatusMutex; auto connectionStatus = make_shared<unordered_map<string, bool>>(); @@ -371,10 +355,9 @@ TEST_F(NostrServiceTest, CloseRelayConnections_ClosesConnections_ToActiveRelays) return status; })); - auto nostrService = make_unique<nostr::NostrService>( + auto nostrService = make_unique<nostr::NostrServiceBase>( testAppender, mockClient, - fakeSigner, defaultTestRelays); nostrService->openRelayConnections(); @@ -387,7 +370,7 @@ TEST_F(NostrServiceTest, CloseRelayConnections_ClosesConnections_ToActiveRelays) ASSERT_EQ(activeRelays.size(), 0); }; -TEST_F(NostrServiceTest, CloseRelayConnections_RemovesClosedConnections_FromActiveRelays) +TEST_F(NostrServiceBaseTest, CloseRelayConnections_RemovesClosedConnections_FromActiveRelays) { vector<string> testRelays = { "wss://nos.lol" }; vector<string> allTestRelays = { defaultTestRelays[0], defaultTestRelays[1], testRelays[0] }; @@ -410,10 +393,9 @@ TEST_F(NostrServiceTest, CloseRelayConnections_RemovesClosedConnections_FromActi return status; })); - auto nostrService = make_unique<nostr::NostrService>( + auto nostrService = make_unique<nostr::NostrServiceBase>( testAppender, mockClient, - fakeSigner, allTestRelays); nostrService->openRelayConnections(); @@ -433,7 +415,7 @@ TEST_F(NostrServiceTest, CloseRelayConnections_RemovesClosedConnections_FromActi } }; -TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_AllSuccesses) +TEST_F(NostrServiceBaseTest, PublishEvent_CorrectlyIndicates_AllSuccesses) { mutex connectionStatusMutex; auto connectionStatus = make_shared<unordered_map<string, bool>>(); @@ -452,10 +434,9 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_AllSuccesses) return status; })); - auto nostrService = make_unique<nostr::NostrService>( + auto nostrService = make_unique<nostr::NostrServiceBase>( testAppender, mockClient, - fakeSigner, defaultTestRelays); nostrService->openRelayConnections(); @@ -484,7 +465,7 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_AllSuccesses) ASSERT_EQ(failures.size(), 0); }; -TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_AllFailures) +TEST_F(NostrServiceBaseTest, PublishEvent_CorrectlyIndicates_AllFailures) { mutex connectionStatusMutex; auto connectionStatus = make_shared<unordered_map<string, bool>>(); @@ -503,10 +484,9 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_AllFailures) return status; })); - auto nostrService = make_unique<nostr::NostrService>( + auto nostrService = make_unique<nostr::NostrServiceBase>( testAppender, mockClient, - fakeSigner, defaultTestRelays); nostrService->openRelayConnections(); @@ -530,7 +510,7 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_AllFailures) } }; -TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_MixedSuccessesAndFailures) +TEST_F(NostrServiceBaseTest, PublishEvent_CorrectlyIndicates_MixedSuccessesAndFailures) { mutex connectionStatusMutex; auto connectionStatus = make_shared<unordered_map<string, bool>>(); @@ -549,10 +529,9 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_MixedSuccessesAndFailur return status; })); - auto nostrService = make_unique<nostr::NostrService>( + auto nostrService = make_unique<nostr::NostrServiceBase>( testAppender, mockClient, - fakeSigner, defaultTestRelays); nostrService->openRelayConnections(); @@ -587,7 +566,7 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_MixedSuccessesAndFailur ASSERT_EQ(failures[0], defaultTestRelays[0]); }; -TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_RejectedEvent) +TEST_F(NostrServiceBaseTest, PublishEvent_CorrectlyIndicates_RejectedEvent) { mutex connectionStatusMutex; auto connectionStatus = make_shared<unordered_map<string, bool>>(); @@ -606,10 +585,9 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_RejectedEvent) return status; })); - auto nostrService = make_unique<nostr::NostrService>( + auto nostrService = make_unique<nostr::NostrServiceBase>( testAppender, mockClient, - fakeSigner, defaultTestRelays); nostrService->openRelayConnections(); @@ -637,7 +615,7 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_RejectedEvent) } }; -TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_EventRejectedBySomeRelays) +TEST_F(NostrServiceBaseTest, PublishEvent_CorrectlyIndicates_EventRejectedBySomeRelays) { mutex connectionStatusMutex; auto connectionStatus = make_shared<unordered_map<string, bool>>(); @@ -656,10 +634,9 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_EventRejectedBySomeRela return status; })); - auto nostrService = make_unique<nostr::NostrService>( + auto nostrService = make_unique<nostr::NostrServiceBase>( testAppender, mockClient, - fakeSigner, defaultTestRelays); nostrService->openRelayConnections(); @@ -700,7 +677,7 @@ TEST_F(NostrServiceTest, PublishEvent_CorrectlyIndicates_EventRejectedBySomeRela ASSERT_EQ(failures[0], defaultTestRelays[1]); }; -TEST_F(NostrServiceTest, QueryRelays_ReturnsEvents_UpToEOSE) +TEST_F(NostrServiceBaseTest, QueryRelays_ReturnsEvents_UpToEOSE) { mutex connectionStatusMutex; auto connectionStatus = make_shared<unordered_map<string, bool>>(); @@ -719,32 +696,28 @@ TEST_F(NostrServiceTest, QueryRelays_ReturnsEvents_UpToEOSE) return status; })); - auto signer = make_unique<FakeSigner>(); - auto nostrService = make_unique<nostr::NostrService>( + auto nostrService = make_unique<nostr::NostrServiceBase>( testAppender, mockClient, - fakeSigner, defaultTestRelays); nostrService->openRelayConnections(); auto testEvents = getMultipleTextNoteTestEvents(); - vector<shared_ptr<nostr::data::Event>> signedTestEvents; + vector<shared_ptr<nostr::data::Event>> sendableTestEvents; for (nostr::data::Event testEvent : testEvents) { - auto signedEvent = make_shared<nostr::data::Event>(testEvent); - signer->sign(signedEvent); - - auto serializedEvent = signedEvent->serialize(); + auto event = make_shared<nostr::data::Event>(testEvent); + auto serializedEvent = event->serialize(); auto deserializedEvent = nostr::data::Event::fromString(serializedEvent); - signedEvent = make_shared<nostr::data::Event>(deserializedEvent); - signedTestEvents.push_back(signedEvent); + event = make_shared<nostr::data::Event>(deserializedEvent); + sendableTestEvents.push_back(event); } // Expect the query messages. EXPECT_CALL(*mockClient, send(HasSubstr("REQ"), _, _)) .Times(2) - .WillRepeatedly(Invoke([&testEvents, &signer]( + .WillRepeatedly(Invoke([&testEvents]( string message, string uri, function<void(const string&)> messageHandler) @@ -755,7 +728,6 @@ TEST_F(NostrServiceTest, QueryRelays_ReturnsEvents_UpToEOSE) for (auto event : testEvents) { auto sendableEvent = make_shared<nostr::data::Event>(event); - signer->sign(sendableEvent); json jarr = json::array({ "EVENT", subscriptionId, sendableEvent->serialize() }); messageHandler(jarr.dump()); } @@ -784,20 +756,20 @@ TEST_F(NostrServiceTest, QueryRelays_ReturnsEvents_UpToEOSE) { ASSERT_NE( find_if( - signedTestEvents.begin(), - signedTestEvents.end(), + sendableTestEvents.begin(), + sendableTestEvents.end(), [&resultEvent](shared_ptr<nostr::data::Event> testEvent) { return *testEvent == *resultEvent; }), - signedTestEvents.end()); + sendableTestEvents.end()); } auto subscriptions = nostrService->subscriptions(); ASSERT_TRUE(subscriptions.empty()); }; -TEST_F(NostrServiceTest, QueryRelays_CallsHandler_WithReturnedEvents) +TEST_F(NostrServiceBaseTest, QueryRelays_CallsHandler_WithReturnedEvents) { mutex connectionStatusMutex; auto connectionStatus = make_shared<unordered_map<string, bool>>(); @@ -816,31 +788,27 @@ TEST_F(NostrServiceTest, QueryRelays_CallsHandler_WithReturnedEvents) return status; })); - auto signer = make_unique<FakeSigner>(); - auto nostrService = make_unique<nostr::NostrService>( + auto nostrService = make_unique<nostr::NostrServiceBase>( testAppender, mockClient, - fakeSigner, defaultTestRelays); nostrService->openRelayConnections(); auto testEvents = getMultipleTextNoteTestEvents(); - vector<shared_ptr<nostr::data::Event>> signedTestEvents; + vector<shared_ptr<nostr::data::Event>> sendableTestEvents; for (nostr::data::Event testEvent : testEvents) { - auto signedEvent = make_shared<nostr::data::Event>(testEvent); - signer->sign(signedEvent); - - auto serializedEvent = signedEvent->serialize(); + auto event = make_shared<nostr::data::Event>(testEvent); + auto serializedEvent = event->serialize(); auto deserializedEvent = nostr::data::Event::fromString(serializedEvent); - signedEvent = make_shared<nostr::data::Event>(deserializedEvent); - signedTestEvents.push_back(signedEvent); + event = make_shared<nostr::data::Event>(deserializedEvent); + sendableTestEvents.push_back(event); } EXPECT_CALL(*mockClient, send(HasSubstr("REQ"), _, _)) .Times(2) - .WillRepeatedly(Invoke([&testEvents, &signer]( + .WillRepeatedly(Invoke([&testEvents]( string message, string uri, function<void(const string&)> messageHandler) @@ -851,7 +819,6 @@ TEST_F(NostrServiceTest, QueryRelays_CallsHandler_WithReturnedEvents) for (auto event : testEvents) { auto sendableEvent = make_shared<nostr::data::Event>(event); - signer->sign(sendableEvent); json jarr = json::array({ "EVENT", subscriptionId, sendableEvent->serialize() }); messageHandler(jarr.dump()); } @@ -869,18 +836,18 @@ TEST_F(NostrServiceTest, QueryRelays_CallsHandler_WithReturnedEvents) string generatedSubscriptionId = nostrService->queryRelays( filters, - [&generatedSubscriptionId, &signedTestEvents](const string& subscriptionId, shared_ptr<nostr::data::Event> event) + [&generatedSubscriptionId, &sendableTestEvents](const string& subscriptionId, shared_ptr<nostr::data::Event> event) { ASSERT_STREQ(subscriptionId.c_str(), generatedSubscriptionId.c_str()); ASSERT_NE( find_if( - signedTestEvents.begin(), - signedTestEvents.end(), + sendableTestEvents.begin(), + sendableTestEvents.end(), [&event](shared_ptr<nostr::data::Event> testEvent) { return *testEvent == *event; }), - signedTestEvents.end()); + sendableTestEvents.end()); }, [&generatedSubscriptionId, &eoseReceivedPromise, &eoseCount] (const string& subscriptionId) @@ -917,7 +884,7 @@ TEST_F(NostrServiceTest, QueryRelays_CallsHandler_WithReturnedEvents) ASSERT_TRUE(subscriptions.empty()); }; -TEST_F(NostrServiceTest, Service_MaintainsMultipleSubscriptions_ThenClosesAll) +TEST_F(NostrServiceBaseTest, Service_MaintainsMultipleSubscriptions_ThenClosesAll) { // Mock connections. mutex connectionStatusMutex; @@ -937,33 +904,29 @@ TEST_F(NostrServiceTest, Service_MaintainsMultipleSubscriptions_ThenClosesAll) return status; })); - auto signer = make_unique<FakeSigner>(); - auto nostrService = make_unique<nostr::NostrService>( + auto nostrService = make_unique<nostr::NostrServiceBase>( testAppender, mockClient, - fakeSigner, testRelays); nostrService->openRelayConnections(); // Mock relay responses. auto testEvents = getMultipleTextNoteTestEvents(); - vector<shared_ptr<nostr::data::Event>> signedTestEvents; + vector<shared_ptr<nostr::data::Event>> sendableTestEvents; for (nostr::data::Event testEvent : testEvents) { - auto signedEvent = make_shared<nostr::data::Event>(testEvent); - signer->sign(signedEvent); - - auto serializedEvent = signedEvent->serialize(); + auto event = make_shared<nostr::data::Event>(testEvent); + auto serializedEvent = event->serialize(); auto deserializedEvent = nostr::data::Event::fromString(serializedEvent); - signedEvent = make_shared<nostr::data::Event>(deserializedEvent); - signedTestEvents.push_back(signedEvent); + event = make_shared<nostr::data::Event>(deserializedEvent); + sendableTestEvents.push_back(event); } vector<string> subscriptionIds; EXPECT_CALL(*mockClient, send(HasSubstr("REQ"), _, _)) .Times(2) - .WillOnce(Invoke([&testEvents, &signer, &subscriptionIds]( + .WillOnce(Invoke([&testEvents, &subscriptionIds]( string message, string uri, function<void(const string&)> messageHandler) @@ -974,7 +937,6 @@ TEST_F(NostrServiceTest, Service_MaintainsMultipleSubscriptions_ThenClosesAll) for (auto event : testEvents) { auto sendableEvent = make_shared<nostr::data::Event>(event); - signer->sign(sendableEvent); json jarr = json::array({ "EVENT", subscriptionIds.at(0), sendableEvent->serialize() }); messageHandler(jarr.dump()); } @@ -984,7 +946,7 @@ TEST_F(NostrServiceTest, Service_MaintainsMultipleSubscriptions_ThenClosesAll) return make_tuple(uri, true); })) - .WillOnce(Invoke([&testEvents, &signer, &subscriptionIds]( + .WillOnce(Invoke([&testEvents, &subscriptionIds]( string message, string uri, function<void(const string&)> messageHandler) @@ -995,7 +957,6 @@ TEST_F(NostrServiceTest, Service_MaintainsMultipleSubscriptions_ThenClosesAll) for (auto event : testEvents) { auto sendableEvent = make_shared<nostr::data::Event>(event); - signer->sign(sendableEvent); json jarr = json::array({ "EVENT", subscriptionIds.at(1), sendableEvent->serialize() }); messageHandler(jarr.dump()); } @@ -1016,18 +977,18 @@ TEST_F(NostrServiceTest, Service_MaintainsMultipleSubscriptions_ThenClosesAll) string shortFormSubscriptionId = nostrService->queryRelays( shortFormFilters, - [&shortFormSubscriptionId, &signedTestEvents](const string& subscriptionId, shared_ptr<nostr::data::Event> event) + [&shortFormSubscriptionId, &sendableTestEvents](const string& subscriptionId, shared_ptr<nostr::data::Event> event) { ASSERT_STREQ(subscriptionId.c_str(), shortFormSubscriptionId.c_str()); ASSERT_NE( find_if( - signedTestEvents.begin(), - signedTestEvents.end(), + sendableTestEvents.begin(), + sendableTestEvents.end(), [&event](shared_ptr<nostr::data::Event> testEvent) { return *testEvent == *event; }), - signedTestEvents.end()); + sendableTestEvents.end()); }, [&shortFormSubscriptionId, &shortFormPromise] (const string& subscriptionId) @@ -1038,18 +999,18 @@ TEST_F(NostrServiceTest, Service_MaintainsMultipleSubscriptions_ThenClosesAll) [](const string&, const string&) {}); string longFormSubscriptionId = nostrService->queryRelays( shortFormFilters, - [&longFormSubscriptionId, &signedTestEvents](const string& subscriptionId, shared_ptr<nostr::data::Event> event) + [&longFormSubscriptionId, &sendableTestEvents](const string& subscriptionId, shared_ptr<nostr::data::Event> event) { ASSERT_STREQ(subscriptionId.c_str(), longFormSubscriptionId.c_str()); ASSERT_NE( find_if( - signedTestEvents.begin(), - signedTestEvents.end(), + sendableTestEvents.begin(), + sendableTestEvents.end(), [&event](shared_ptr<nostr::data::Event> testEvent) { return *testEvent == *event; }), - signedTestEvents.end()); + sendableTestEvents.end()); }, [&longFormSubscriptionId, &longFormPromise] (const string& subscriptionId) |