diff options
Diffstat (limited to 'include')
-rw-r--r-- | include/client/web_socket_client.hpp | 3 | ||||
-rw-r--r-- | include/data/data.hpp | 114 | ||||
-rw-r--r-- | include/nostr.hpp | 130 | ||||
-rw-r--r-- | include/signer/signer.hpp | 31 |
4 files changed, 158 insertions, 120 deletions
diff --git a/include/client/web_socket_client.hpp b/include/client/web_socket_client.hpp index 63fa634..e186e90 100644 --- a/include/client/web_socket_client.hpp +++ b/include/client/web_socket_client.hpp @@ -6,6 +6,8 @@ #include <websocketpp/client.hpp> #include <websocketpp/config/asio_client.hpp> +namespace nostr +{ namespace client { /** @@ -72,3 +74,4 @@ public: virtual void closeConnection(std::string uri) = 0; }; } // namespace client +} // namespace nostr diff --git a/include/data/data.hpp b/include/data/data.hpp new file mode 100644 index 0000000..46156cd --- /dev/null +++ b/include/data/data.hpp @@ -0,0 +1,114 @@ +#pragma once + +#include <chrono> +#include <string> +#include <unordered_map> +#include <vector> + +#include <nlohmann/json.hpp> +#include <openssl/evp.h> +#include <openssl/sha.h> + +namespace nostr +{ +namespace data +{ +/** + * @brief A Nostr event. + * @remark All data transmitted over the Nostr protocol is encoded in JSON blobs. This struct + * is common to every Nostr event kind. The significance of each event is determined by the + * `tags` and `content` fields. +*/ +struct Event +{ + std::string id; ///< SHA-256 hash of the event data. + std::string pubkey; ///< Public key of the event creator. + std::time_t createdAt; ///< Unix timestamp of the event creation. + int kind; ///< Event kind. + std::vector<std::vector<std::string>> tags; ///< Arbitrary event metadata. + std::string content; ///< Event content. + std::string sig; ///< Event signature created with the private key of the event creator. + + /** + * @brief Serializes the event to a JSON object. + * @returns A stringified JSON object representing the event. + * @throws `std::invalid_argument` if the event object is invalid. + */ + std::string serialize(); + + /** + * @brief Deserializes the event from a JSON string. + * @param jsonString A stringified JSON object representing the event. + * @returns An event instance created from the JSON string. + */ + static Event fromString(std::string jsonString); + + /** + * @brief Deserializes the event from a JSON object. + * @param j A JSON object representing the event. + * @returns An event instance created from the JSON object. + */ + static Event fromJson(nlohmann::json j); + + /** + * @brief Compares two events for equality. + * @remark Two events are considered equal if they have the same ID, since the ID is uniquely + * generated from the event data. If the `id` field is empty for either event, the comparison + * function will throw an exception. + */ + bool operator==(const Event& other) const; + +private: + /** + * @brief Validates the event. + * @throws `std::invalid_argument` if the event object is invalid. + * @remark The `createdAt` field defaults to the present if it is not already set. + */ + void validate(); + + /** + * @brief Generates an ID for the event. + * @param serializedData The serialized JSON string of all of the event data except the ID and + * the signature. + * @return A valid Nostr event ID. + * @remark The ID is a 32-bytes lowercase hex-encoded sha256 of the serialized event data. + */ + std::string generateId(std::string serializedData) const; +}; + +/** + * @brief A set of filters for querying Nostr relays. + * @remark The `limit` field should always be included to keep the response size reasonable. The + * `since` field is not required, and the `until` field will default to the present. At least one + * of the other fields must be set for a valid filter. + */ +struct Filters +{ + std::vector<std::string> ids; ///< Event IDs. + std::vector<std::string> authors; ///< Event author npubs. + std::vector<int> kinds; ///< Kind numbers. + std::unordered_map<std::string, std::vector<std::string>> tags; ///< Tag names mapped to lists of tag values. + std::time_t since; ///< Unix timestamp. Matching events must be newer than this. + std::time_t until; ///< Unix timestamp. Matching events must be older than this. + int limit; ///< The maximum number of events the relay should return on the initial query. + + /** + * @brief Serializes the filters to a JSON object. + * @param subscriptionId A string up to 64 chars in length that is unique per relay connection. + * @returns A stringified JSON object representing the filters. + * @throws `std::invalid_argument` if the filter object is invalid. + * @remarks The Nostr client is responsible for managing subscription IDs. Responses from the + * relay will be organized by subscription ID. + */ + std::string serialize(std::string& subscriptionId); + +private: + /** + * @brief Validates the filters. + * @throws `std::invalid_argument` if the filter object is invalid. + * @remark The `until` field defaults to the present if it is not already set. + */ + void validate(); +}; +} // namespace data +} // namespace nostr diff --git a/include/nostr.hpp b/include/nostr.hpp index e5b29c7..76bacd9 100644 --- a/include/nostr.hpp +++ b/include/nostr.hpp @@ -9,8 +9,6 @@ #include <vector> #include <nlohmann/json.hpp> -#include <openssl/evp.h> -#include <openssl/sha.h> #include <plog/Init.h> #include <plog/Log.h> #include <websocketpp/client.hpp> @@ -18,121 +16,24 @@ #include <uuid_v4.h> #include "client/web_socket_client.hpp" +#include "data/data.hpp" +#include "signer/signer.hpp" namespace nostr { -class ISigner; class NostrService; -/** - * @brief A Nostr event. - * @remark All data transmitted over the Nostr protocol is encoded in JSON blobs. This struct - * is common to every Nostr event kind. The significance of each event is determined by the - * `tags` and `content` fields. -*/ -struct Event -{ - std::string id; ///< SHA-256 hash of the event data. - std::string pubkey; ///< Public key of the event creator. - std::time_t createdAt; ///< Unix timestamp of the event creation. - int kind; ///< Event kind. - std::vector<std::vector<std::string>> tags; ///< Arbitrary event metadata. - std::string content; ///< Event content. - std::string sig; ///< Event signature created with the private key of the event creator. - - /** - * @brief Serializes the event to a JSON object. - * @returns A stringified JSON object representing the event. - * @throws `std::invalid_argument` if the event object is invalid. - */ - std::string serialize(); - - /** - * @brief Deserializes the event from a JSON string. - * @param jsonString A stringified JSON object representing the event. - * @returns An event instance created from the JSON string. - */ - static Event fromString(std::string jsonString); - - /** - * @brief Deserializes the event from a JSON object. - * @param j A JSON object representing the event. - * @returns An event instance created from the JSON object. - */ - static Event fromJson(nlohmann::json j); - - /** - * @brief Compares two events for equality. - * @remark Two events are considered equal if they have the same ID, since the ID is uniquely - * generated from the event data. If the `id` field is empty for either event, the comparison - * function will throw an exception. - */ - bool operator==(const Event& other) const; - -private: - /** - * @brief Validates the event. - * @throws `std::invalid_argument` if the event object is invalid. - * @remark The `createdAt` field defaults to the present if it is not already set. - */ - void validate(); - - /** - * @brief Generates an ID for the event. - * @param serializedData The serialized JSON string of all of the event data except the ID and - * the signature. - * @return A valid Nostr event ID. - * @remark The ID is a 32-bytes lowercase hex-encoded sha256 of the serialized event data. - */ - std::string generateId(std::string serializedData) const; -}; - -/** - * @brief A set of filters for querying Nostr relays. - * @remark The `limit` field should always be included to keep the response size reasonable. The - * `since` field is not required, and the `until` field will default to the present. At least one - * of the other fields must be set for a valid filter. - */ -struct Filters -{ - std::vector<std::string> ids; ///< Event IDs. - std::vector<std::string> authors; ///< Event author npubs. - std::vector<int> kinds; ///< Kind numbers. - std::unordered_map<std::string, std::vector<std::string>> tags; ///< Tag names mapped to lists of tag values. - std::time_t since; ///< Unix timestamp. Matching events must be newer than this. - std::time_t until; ///< Unix timestamp. Matching events must be older than this. - int limit; ///< The maximum number of events the relay should return on the initial query. - - /** - * @brief Serializes the filters to a JSON object. - * @param subscriptionId A string up to 64 chars in length that is unique per relay connection. - * @returns A stringified JSON object representing the filters. - * @throws `std::invalid_argument` if the filter object is invalid. - * @remarks The Nostr client is responsible for managing subscription IDs. Responses from the - * relay will be organized by subscription ID. - */ - std::string serialize(std::string& subscriptionId); - -private: - /** - * @brief Validates the filters. - * @throws `std::invalid_argument` if the filter object is invalid. - * @remark The `until` field defaults to the present if it is not already set. - */ - void validate(); -}; - class NostrService { public: NostrService( std::shared_ptr<plog::IAppender> appender, std::shared_ptr<client::IWebSocketClient> client, - std::shared_ptr<ISigner> signer); + std::shared_ptr<signer::ISigner> signer); NostrService( std::shared_ptr<plog::IAppender> appender, std::shared_ptr<client::IWebSocketClient> client, - std::shared_ptr<ISigner> signer, + std::shared_ptr<signer::ISigner> signer, std::vector<std::string> relays); ~NostrService(); @@ -171,7 +72,7 @@ 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<Event> event); + std::tuple<std::vector<std::string>, std::vector<std::string>> publishEvent(std::shared_ptr<data::Event> event); /** * @brief Queries all open relay connections for events matching the given set of filters, and @@ -185,7 +86,7 @@ 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<Event>> queryRelays(std::shared_ptr<Filters> filters); + std::vector<std::shared_ptr<data::Event>> queryRelays(std::shared_ptr<data::Filters> filters); /** * @brief Queries all open relay connections for events matching the given set of filters. @@ -202,8 +103,8 @@ public: * events, and they will not be accessible via `getNewEvents`. */ std::string queryRelays( - std::shared_ptr<Filters> filters, - std::function<void(const std::string&, std::shared_ptr<Event>)> eventHandler, + 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); @@ -242,7 +143,7 @@ private: ///< The WebSocket client used to communicate with relays. std::shared_ptr<client::IWebSocketClient> _client; ///< The signer used to sign Nostr events. - std::shared_ptr<ISigner> _signer; + std::shared_ptr<signer::ISigner> _signer; ///< A mutex to protect the instance properties. std::mutex _propertyMutex; @@ -324,7 +225,7 @@ private: */ void onSubscriptionMessage( std::string message, - std::function<void(const std::string&, std::shared_ptr<Event>)> eventHandler, + 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); @@ -336,15 +237,4 @@ private: */ void onAcceptance(std::string message, std::function<void(const bool)> acceptanceHandler); }; - -class ISigner -{ -public: - /** - * @brief Signs the given Nostr event. - * @param event The event to sign. - * @remark The event's `sig` field will be updated in-place with the signature. - */ - virtual void sign(std::shared_ptr<Event> event) = 0; -}; } // namespace nostr diff --git a/include/signer/signer.hpp b/include/signer/signer.hpp new file mode 100644 index 0000000..e68df93 --- /dev/null +++ b/include/signer/signer.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "data/data.hpp" + +namespace nostr +{ +namespace signer +{ +/** + * @brief An interface for Nostr event signing that implements NIP-46. + */ +class ISigner +{ +public: + virtual void receiveConnection(std::string connectionToken) = 0; + + virtual void initiateConnection( + std::string relay, + std::string name, + std::string url, + std::string description) = 0; + + /** + * @brief Signs the given Nostr event. + * @param event The event to sign. + * @remark The event's `sig` field will be updated in-place with the signature. + */ + virtual void sign(std::shared_ptr<nostr::data::Event> event) = 0; +}; +} // namespace signer +} // namespace nostr |