aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/signer/noscrypt_signer.hpp52
-rw-r--r--include/signer/signer.hpp6
-rw-r--r--src/data/event.cpp1
-rw-r--r--src/signer/noscrypt_signer.cpp209
4 files changed, 233 insertions, 35 deletions
diff --git a/include/signer/noscrypt_signer.hpp b/include/signer/noscrypt_signer.hpp
index 7201c12..de8aab4 100644
--- a/include/signer/noscrypt_signer.hpp
+++ b/include/signer/noscrypt_signer.hpp
@@ -31,6 +31,10 @@ public:
std::shared_ptr<std::promise<bool>> sign(std::shared_ptr<data::Event> event) override;
private:
+ const int _nostrConnectKind = 24133; // Kind 24133 is reserved for NIP-46 events.
+
+ Encryption _nostrConnectEncryption;
+
std::shared_ptr<NCContext> _noscryptContext;
std::shared_ptr<nostr::service::INostrServiceBase> _nostrService;
@@ -85,6 +89,29 @@ private:
*/
std::string _generateSignerRequestId();
+ /**
+ * @brief Builds a wrapper event for JRPC-like signer messages.
+ * @param jrpc The JRPC-like payload that will comprise the event content, as specified by
+ * NIP-46.
+ * @returns A shared pointer to the wrapper event.
+ */
+ std::shared_ptr<nostr::data::Event> _wrapSignerMessage(nlohmann::json jrpc);
+
+ /**
+ * @brief Unwraps the JRPC-like payload from a signer message, typically one received from the
+ * remote signer in response to a request.
+ * @param event An event containing a NIP-46 message payload.
+ * @returns The unwrapped payload. The returned object will be empty if no valid payload could
+ * be extracted from the given event.
+ */
+ std::string _unwrapSignerMessage(std::shared_ptr<nostr::data::Event> event);
+
+ /**
+ * @brief Pings the remote signer to confirm that it is online and available.
+ * @returns `true` if the signer is available, `false` otherwise.
+ */
+ bool _pingSigner();
+
#pragma region Cryptography
/**
@@ -93,15 +120,36 @@ private:
*/
void _reseedRandomNumberGenerator(uint32_t bufferSize = 32);
- std::string _encryptNip04();
+ /**
+ * @brief Encrypts a string according to the standard specified in NIP-04.
+ * @param input The string to be encrypted.
+ * @return The resulting encrypted string, or an empty string if the input could not be
+ * encrypted.
+ */
+ std::string _encryptNip04(const std::string input);
+
+ /**
+ * @brief Decrypts a NIP-04 encrypted string.
+ * @param input The string to be decrypted.
+ * @return The decrypted string, or an empty string if the input could not be decrypted.
+ */
+ std::string _decryptNip04(const std::string input);
/**
* @brief Encrypts a string according to the standard specified in NIP-44.
* @param input The string to be encrypted.
- * @return The encrypted input.
+ * @return The resulting encrypted string, or an empty string if the input could not be
+ * encrypted.
*/
std::string _encryptNip44(const std::string input); // TODO: Return or set HMAC?
+ /**
+ * @brief Decrypts a NIP-44 encrypted string.
+ * @param input The string to be decrypted.
+ * @return The decrypted string, or an empty string if the input could not be decrypted.
+ */
+ std::string _decryptNip44(const std::string input);
+
#pragma endregion
#pragma region Logging
diff --git a/include/signer/signer.hpp b/include/signer/signer.hpp
index 0faaff8..10e54c6 100644
--- a/include/signer/signer.hpp
+++ b/include/signer/signer.hpp
@@ -11,6 +11,12 @@ namespace nostr
{
namespace signer
{
+enum class Encryption
+{
+ NIP04,
+ NIP44
+};
+
/**
* @brief An interface for Nostr event signing that implements NIP-46.
*/
diff --git a/src/data/event.cpp b/src/data/event.cpp
index d101531..a1a96f1 100644
--- a/src/data/event.cpp
+++ b/src/data/event.cpp
@@ -7,6 +7,7 @@ using namespace nlohmann;
using namespace nostr::data;
using namespace std;
+// TODO: Verify event signature using noscrypt.
string Event::serialize()
{
try
diff --git a/src/signer/noscrypt_signer.cpp b/src/signer/noscrypt_signer.cpp
index 1ac3b88..4d39393 100644
--- a/src/signer/noscrypt_signer.cpp
+++ b/src/signer/noscrypt_signer.cpp
@@ -114,43 +114,36 @@ string NoscryptSigner::initiateConnection(
shared_ptr<promise<bool>> NoscryptSigner::sign(shared_ptr<Event> event)
{
- const int nostrConnectKind = 24133; // Kind 24133 is reserved for NIP-46 events.
-
auto signingPromise = make_shared<promise<bool>>();
+ bool signerAvailable = this->_pingSigner();
+ if (!signerAvailable)
+ {
+ PLOG_ERROR << "Ping to the remote signer failed - the remote signer may be unavailable.";
+ signingPromise->set_value(false);
+ return signingPromise;
+ }
+
// Create the JSON-RPC-like message content.
auto params = nlohmann::json::array();
params.push_back(event->serialize());
auto requestId = this->_generateSignerRequestId();
- nlohmann::json jrpc = {
- { "id", requestId },
- { "method", "sign_event" },
- { "params", params }
- };
-
- // TODO: Encrypt the message content with NIP-04 (or NIP-44).
- NCEncryptionArgs encryptionArgs;
-
- string encryptedContent;
-
- // Wrap the event to be signed in a signing request event.
- auto signingRequest = make_shared<Event>();
- signingRequest->pubkey = this->_localPublicKey;
- signingRequest->kind = nostrConnectKind;
- signingRequest->tags.push_back({ "p", this->_remotePublicKey });
- signingRequest->content = encryptedContent;
- // TODO: Sign the wrapper event with the local private key.
-
// Create a filter set to find events from the remote signer.
auto remoteSignerFilters = make_shared<Filters>();
- remoteSignerFilters->kinds.push_back(nostrConnectKind);
+ remoteSignerFilters->kinds.push_back(this->_nostrConnectKind);
remoteSignerFilters->since = time(nullptr); // Filter for new signer events.
remoteSignerFilters->tags["p"] = { this->_localPublicKey }; // Signer events tag the local npub.
remoteSignerFilters->limit = 1; // We only need the immediate response to the signing request.
- // TODO: Ping the remote signer before sending the request.
+ // Generate the signing request event.
+ nlohmann::json jrpc = {
+ { "id", requestId },
+ { "method", "sign_event" },
+ { "params", params }
+ };
+ auto signingRequest = this->_wrapSignerMessage(jrpc);
// Send the signing request.
this->_nostrService->publishEvent(signingRequest);
@@ -158,15 +151,22 @@ shared_ptr<promise<bool>> NoscryptSigner::sign(shared_ptr<Event> event)
// Wait for the remote signer's response.
this->_nostrService->queryRelays(
remoteSignerFilters,
- [this, &signingPromise](const string&, shared_ptr<Event> event)
+ [this, &event, &signingPromise](const string&, shared_ptr<Event> signerEvent)
{
- // TODO: Handle the response from the remote signer.
-
- // Eventually:
+ // Assign the response event to the `event` parameter, accomplishing the intended
+ // function result via side effect.
+ string signerResponse = this->_unwrapSignerMessage(signerEvent);
+ event = make_shared<Event>(Event::fromString(signerResponse));
signingPromise->set_value(true);
},
- nullptr,
- nullptr);
+ [&signingPromise](const string&)
+ {
+ signingPromise->set_value(false);
+ },
+ [&signingPromise](const string&, const string&)
+ {
+ signingPromise->set_value(false);
+ });
return signingPromise;
};
@@ -326,6 +326,104 @@ string NoscryptSigner::_generateSignerRequestId()
return uuid.str();
};
+shared_ptr<Event> NoscryptSigner::_wrapSignerMessage(nlohmann::json jrpc)
+{
+ // Encrypt the message payload.
+ string encryptedContent;
+ switch (this->_nostrConnectEncryption)
+ {
+ case Encryption::NIP44:
+ encryptedContent = this->_encryptNip44(jrpc.dump());
+ if (!encryptedContent.empty())
+ {
+ break;
+ }
+
+ // Use NIP-04 encryption as a fallback.
+ case Encryption::NIP04:
+ encryptedContent = this->_encryptNip04(jrpc.dump());
+ break;
+ }
+
+ // Wrap the event to be signed in a signing request event.
+ auto wrapperEvent = make_shared<Event>();
+ wrapperEvent->pubkey = this->_localPublicKey;
+ wrapperEvent->kind = this->_nostrConnectKind;
+ wrapperEvent->tags.push_back({ "p", this->_remotePublicKey });
+ wrapperEvent->content = encryptedContent;
+
+ // Generate a random seed for the signer.
+ shared_ptr<uint8_t> random32(new uint8_t[32]);
+ int code = RAND_bytes(random32.get(), 32);
+ if (code <= 0)
+ {
+ PLOG_ERROR << "Failed to generate a random 32-byte seed buffer for the signer.";
+ return nullptr;
+ }
+
+ // Sign the wrapper message with the local secret key.
+ string serializedEvent = wrapperEvent->serialize();
+ unique_ptr<uint8_t> dataToSign((uint8_t*)serializedEvent.c_str());
+ uint32_t dataSize = serializedEvent.length();
+ unique_ptr<uint8_t> signature(new uint8_t[64]);
+ NCResult signatureResult = NCSignData(
+ this->_noscryptContext.get(),
+ this->_localSecret.get(),
+ random32.get(),
+ dataToSign.get(),
+ dataSize,
+ signature.get());
+
+ // TODO: Handle result codes.
+ if (signatureResult != NC_SUCCESS)
+ {
+ return nullptr;
+ }
+
+ // Add the signature to the event.
+ wrapperEvent->sig = string((char*)signature.get(), 64);
+
+ return wrapperEvent;
+};
+
+string NoscryptSigner::_unwrapSignerMessage(shared_ptr<Event> event)
+{
+ // TODO: Verify the incoming event.
+
+ // Extract and decrypt the event payload.
+ string encryptedContent = event->content;
+ string decryptedContent;
+
+ // NIP-04 encrypted strings include `?iv=` near the end (source: hodlbod).
+ if (encryptedContent.find("?iv=") != string::npos)
+ {
+ decryptedContent = this->_decryptNip04(encryptedContent);
+ }
+ else
+ {
+ decryptedContent = this->_decryptNip44(encryptedContent);
+ }
+
+ // Parse the decrypted string into a JSON object.
+ return decryptedContent;
+};
+
+bool NoscryptSigner::_pingSigner()
+{
+ nlohmann::json jrpc =
+ {
+ { "id", this->_generateSignerRequestId() },
+ { "method", "ping" },
+ { "params", nlohmann::json::array() }
+ };
+
+ auto messageEvent = this->_wrapSignerMessage(jrpc);
+
+ this->_nostrService->publishEvent(messageEvent);
+
+ // TODO: Handle the relay response.
+};
+
#pragma region Cryptography
void NoscryptSigner::_reseedRandomNumberGenerator(uint32_t bufferSize)
@@ -338,7 +436,7 @@ void NoscryptSigner::_reseedRandomNumberGenerator(uint32_t bufferSize)
}
};
-string NoscryptSigner::_encryptNip04()
+string NoscryptSigner::_encryptNip04(std::string input)
{
throw runtime_error("NIP-04 encryption is not yet implemented.");
};
@@ -362,7 +460,7 @@ string NoscryptSigner::_encryptNip44(const string input)
}
// Setup the encryption context.
- NCEncryptionArgs encryptionArgs =
+ unique_ptr<NCEncryptionArgs> encryptionArgs(new NCEncryptionArgs
{
nonce.get(),
hmacKey.get(),
@@ -370,14 +468,14 @@ string NoscryptSigner::_encryptNip44(const string input)
output.get(),
bufferSize,
nip44Version
- };
+ });
// Perform the encryption.
NCResult encryptionResult = NCEncrypt(
this->_noscryptContext.get(),
this->_localSecret.get(),
this->_remotePubkey.get(),
- &encryptionArgs);
+ encryptionArgs.get());
// TODO: Handle various codes.
if (encryptionResult != NC_SUCCESS)
@@ -388,6 +486,51 @@ string NoscryptSigner::_encryptNip44(const string input)
return string((char*)output.get(), bufferSize);
};
+string NoscryptSigner::_decryptNip44(const string input)
+{
+ uint32_t nip44Version = 0x02;
+
+ shared_ptr<uint8_t> nonce(new uint8_t[32]);
+ shared_ptr<uint8_t> hmacKey(new uint8_t[32]);
+
+ uint32_t bufferSize = input.length();
+ shared_ptr<uint8_t> output(new uint8_t[bufferSize]);
+
+ // Generate a nonce to use for the decryption.
+ int code = RAND_bytes(nonce.get(), 32);
+ if (code <= 0)
+ {
+ PLOG_ERROR << "Failed to generate a nonce for NIP-44 decryption.";
+ return string();
+ }
+
+ // Set up the decryption context.
+ unique_ptr<NCEncryptionArgs> decryptionArgs(new NCEncryptionArgs
+ {
+ nonce.get(),
+ hmacKey.get(),
+ (uint8_t*)input.c_str(),
+ output.get(),
+ bufferSize,
+ nip44Version
+ });
+
+ // Perform the decryption.
+ NCResult decryptionResult = NCDecrypt(
+ this->_noscryptContext.get(),
+ this->_localSecret.get(),
+ this->_remotePubkey.get(),
+ decryptionArgs.get());
+
+ // TODO: Handle various codes.
+ if (decryptionResult != NC_SUCCESS)
+ {
+ return string();
+ }
+
+ return string((char*)output.get(), bufferSize);
+};
+
#pragma endregion
#pragma region Logging