Implement NIP-04: Encrypted Direct Messages
This commit is contained in:
		
							parent
							
								
									0df4dbb979
								
							
						
					
					
						commit
						857bf0ce8e
					
				
							
								
								
									
										28
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								README.md
									
									
									
									
									
								
							@ -20,6 +20,7 @@ has not yet reached a stable release. Use with caution.
 | 
			
		||||
  * [Stop previous subscriptions](#stop-previous-subscriptions)
 | 
			
		||||
  * [Publishing an event](#publishing-an-event)
 | 
			
		||||
  * [Creating/updating your contact list](#creatingupdating-your-contact-list)
 | 
			
		||||
  * [Sending an encrypted direct message](#sending-an-encrypted-direct-message)
 | 
			
		||||
- [Implemented NIPs](#implemented-nips)
 | 
			
		||||
- [Development](#development)
 | 
			
		||||
  * [Type checking](#type-checking)
 | 
			
		||||
@ -240,6 +241,33 @@ update_contacts_event = user.create_event(
 | 
			
		||||
client.publish(update_contacts_event)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Sending an encrypted direct message
 | 
			
		||||
 | 
			
		||||
```ruby
 | 
			
		||||
sender_private_key = '3185a47e3802f956ca5a2b4ea606c1d51c7610f239617e8f0f218d55bdf2b757'
 | 
			
		||||
 | 
			
		||||
encrypted_direct_message = Nostr::Events::EncryptedDirectMessage.new(
 | 
			
		||||
   sender_private_key: sender_private_key,
 | 
			
		||||
   recipient_public_key: '6c31422248998e300a1a457167565da7d15d0da96651296ee2791c29c11b6aa0',
 | 
			
		||||
   plain_text: 'Your feedback is appreciated, now pay $8',
 | 
			
		||||
   previous_direct_message: 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460' # optional
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
encrypted_direct_message.sign(sender_private_key)
 | 
			
		||||
 | 
			
		||||
# #<Nostr::Events::EncryptedDirectMessage:0x0000000104c9fa68
 | 
			
		||||
# @content="mjIFNo1sSP3KROE6QqhWnPSGAZRCuK7Np9X+88HSVSwwtFyiZ35msmEVoFgRpKx4?iv=YckChfS2oWCGpMt1uQ4GbQ==",
 | 
			
		||||
#   @created_at=1676456512,
 | 
			
		||||
#   @id="daac98826d5eb29f7c013b6160986c4baf4fe6d4b995df67c1b480fab1839a9b",
 | 
			
		||||
#   @kind=4,
 | 
			
		||||
#   @pubkey="8a9d69c56e3c691bec8f9565e4dcbe38ae1d88fffeec3ce66b9f47558a3aa8ca",
 | 
			
		||||
#   @sig="028bb5f5bab0396e2065000c84a4bcce99e68b1a79bb1b91a84311546f49c5b67570b48d4a328a1827e7a8419d74451347d4f55011a196e71edab31aa3d6bdac",
 | 
			
		||||
#   @tags=[["p", "6c31422248998e300a1a457167565da7d15d0da96651296ee2791c29c11b6aa0"], ["e", "ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460"]]>
 | 
			
		||||
 | 
			
		||||
# Send it to the Relay
 | 
			
		||||
client.publish(encrypted_direct_message)
 | 
			
		||||
````
 | 
			
		||||
 | 
			
		||||
## Implemented NIPs
 | 
			
		||||
 | 
			
		||||
- [x] [NIP-01 - Client](https://github.com/nostr-protocol/nips/blob/master/01.md)
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,9 @@ target :lib do
 | 
			
		||||
  check 'lib'
 | 
			
		||||
 | 
			
		||||
  # Core libraries
 | 
			
		||||
  library 'base64'
 | 
			
		||||
  library 'digest'
 | 
			
		||||
  library 'openssl'
 | 
			
		||||
  library 'securerandom'
 | 
			
		||||
 | 
			
		||||
  # Gems
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ require_relative 'nostr/relay'
 | 
			
		||||
require_relative 'nostr/key_pair'
 | 
			
		||||
require_relative 'nostr/event_kind'
 | 
			
		||||
require_relative 'nostr/event'
 | 
			
		||||
require_relative 'nostr/events/encrypted_direct_message'
 | 
			
		||||
require_relative 'nostr/client'
 | 
			
		||||
require_relative 'nostr/user'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,72 @@
 | 
			
		||||
module Nostr
 | 
			
		||||
  # Performs cryptographic operations on a +Nostr::Event+.
 | 
			
		||||
  class Crypto
 | 
			
		||||
    # Numeric base of the OpenSSL big number used in an event content's encryption.
 | 
			
		||||
    #
 | 
			
		||||
    # @return [Integer]
 | 
			
		||||
    #
 | 
			
		||||
    BN_BASE = 16
 | 
			
		||||
 | 
			
		||||
    # Name of the cipher curve used in an event content's encryption.
 | 
			
		||||
    #
 | 
			
		||||
    # @return [String]
 | 
			
		||||
    #
 | 
			
		||||
    CIPHER_CURVE = 'secp256k1'
 | 
			
		||||
 | 
			
		||||
    # Name of the cipher algorithm used in an event content's encryption.
 | 
			
		||||
    #
 | 
			
		||||
    # @return [String]
 | 
			
		||||
    #
 | 
			
		||||
    CIPHER_ALGORITHM = 'aes-256-cbc'
 | 
			
		||||
 | 
			
		||||
    # Encrypts a piece of text
 | 
			
		||||
    #
 | 
			
		||||
    # @api public
 | 
			
		||||
    #
 | 
			
		||||
    # @example Encrypting an event's content
 | 
			
		||||
    #   crypto = Nostr::Crypto.new
 | 
			
		||||
    #   encrypted = crypto.encrypt_text(sender_private_key, recipient_public_key, 'Feedback appreciated. Now pay $8')
 | 
			
		||||
    #   encrypted # => "wrYQaHDfpOEvyJELSCg1vzsywmlJTz8NqH03eFW44s8iQs869jtSb26Lr4s23gmY?iv=v38vAJ3LlJAGZxbmWU4qAg=="
 | 
			
		||||
    #
 | 
			
		||||
    # @param sender_private_key [String] 32-bytes hex-encoded private key of the creator.
 | 
			
		||||
    # @param recipient_public_key [String] 32-bytes hex-encoded public key of the recipient.
 | 
			
		||||
    # @param plain_text [String] The text to be encrypted
 | 
			
		||||
    #
 | 
			
		||||
    # @return [String] Encrypted text.
 | 
			
		||||
    #
 | 
			
		||||
    def encrypt_text(sender_private_key, recipient_public_key, plain_text)
 | 
			
		||||
      cipher = OpenSSL::Cipher.new(CIPHER_ALGORITHM).encrypt
 | 
			
		||||
      cipher.iv = iv = cipher.random_iv
 | 
			
		||||
      cipher.key = compute_shared_key(sender_private_key, recipient_public_key)
 | 
			
		||||
      encrypted_text = cipher.update(plain_text) + cipher.final
 | 
			
		||||
      encrypted_text = "#{Base64.encode64(encrypted_text)}?iv=#{Base64.encode64(iv)}"
 | 
			
		||||
      encrypted_text.gsub("\n", '')
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Decrypts a piece of text
 | 
			
		||||
    #
 | 
			
		||||
    # @api public
 | 
			
		||||
    #
 | 
			
		||||
    # @example Encrypting an event's content
 | 
			
		||||
    #   crypto = Nostr::Crypto.new
 | 
			
		||||
    #   encrypted # => "wrYQaHDfpOEvyJELSCg1vzsywmlJTz8NqH03eFW44s8iQs869jtSb26Lr4s23gmY?iv=v38vAJ3LlJAGZxbmWU4qAg=="
 | 
			
		||||
    #   decrypted = crypto.decrypt_text(recipient_private_key, sender_public_key, encrypted)
 | 
			
		||||
    #
 | 
			
		||||
    # @param sender_public_key [String] 32-bytes hex-encoded public key of the message creator.
 | 
			
		||||
    # @param recipient_private_key [String] 32-bytes hex-encoded public key of the recipient.
 | 
			
		||||
    # @param encrypted_text [String] The text to be decrypted
 | 
			
		||||
    #
 | 
			
		||||
    # @return [String] Decrypted text.
 | 
			
		||||
    #
 | 
			
		||||
    def decrypt_text(recipient_private_key, sender_public_key, encrypted_text)
 | 
			
		||||
      base64_encoded_text, iv = encrypted_text.split('?iv=')
 | 
			
		||||
      cipher = OpenSSL::Cipher.new(CIPHER_ALGORITHM).decrypt
 | 
			
		||||
      cipher.iv = Base64.decode64(iv)
 | 
			
		||||
      cipher.key = compute_shared_key(recipient_private_key, sender_public_key)
 | 
			
		||||
      plain_text = cipher.update(Base64.decode64(base64_encoded_text)) + cipher.final
 | 
			
		||||
      plain_text.force_encoding('UTF-8')
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Uses the private key to generate an event id and sign the event
 | 
			
		||||
    #
 | 
			
		||||
    # @api public
 | 
			
		||||
@ -33,6 +99,35 @@ module Nostr
 | 
			
		||||
 | 
			
		||||
    private
 | 
			
		||||
 | 
			
		||||
    # Finds a shared key between two keys
 | 
			
		||||
    #
 | 
			
		||||
    # @api private
 | 
			
		||||
    #
 | 
			
		||||
    # @param private_key [String] 32-bytes hex-encoded private key.
 | 
			
		||||
    # @param public_key [String] 32-bytes hex-encoded public key.
 | 
			
		||||
    #
 | 
			
		||||
    # @return [String] A shared key used in the event's content encryption and decryption.
 | 
			
		||||
    #
 | 
			
		||||
    def compute_shared_key(private_key, public_key)
 | 
			
		||||
      group = OpenSSL::PKey::EC::Group.new(CIPHER_CURVE)
 | 
			
		||||
 | 
			
		||||
      private_key_bn   = OpenSSL::BN.new(private_key, BN_BASE)
 | 
			
		||||
      public_key_bn    = OpenSSL::BN.new("02#{public_key}", BN_BASE)
 | 
			
		||||
      public_key_point = OpenSSL::PKey::EC::Point.new(group, public_key_bn)
 | 
			
		||||
 | 
			
		||||
      asn1 = OpenSSL::ASN1::Sequence(
 | 
			
		||||
        [
 | 
			
		||||
          OpenSSL::ASN1::Integer.new(1),
 | 
			
		||||
          OpenSSL::ASN1::OctetString(private_key_bn.to_s(2)),
 | 
			
		||||
          OpenSSL::ASN1::ObjectId(CIPHER_CURVE, 0, :EXPLICIT),
 | 
			
		||||
          OpenSSL::ASN1::BitString(public_key_point.to_octet_string(:uncompressed), 1, :EXPLICIT)
 | 
			
		||||
        ]
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
      pkey = OpenSSL::PKey::EC.new(asn1.to_der)
 | 
			
		||||
      pkey.dh_compute_key(public_key_point)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Generates a SHA256 hash of a +Nostr::Event+
 | 
			
		||||
    #
 | 
			
		||||
    # @api private
 | 
			
		||||
 | 
			
		||||
@ -31,5 +31,13 @@ module Nostr
 | 
			
		||||
    # @return [Integer]
 | 
			
		||||
    #
 | 
			
		||||
    CONTACT_LIST = 3
 | 
			
		||||
 | 
			
		||||
    # A special event with kind 4, meaning "encrypted direct message". An event of this kind has its +content+
 | 
			
		||||
    # equal to the base64-encoded, aes-256-cbc encrypted string of anything a user wants to write, encrypted using a
 | 
			
		||||
    # shared cipher generated by combining the recipient's public-key with the sender's private-key.
 | 
			
		||||
    #
 | 
			
		||||
    # @return [Integer]
 | 
			
		||||
    #
 | 
			
		||||
    ENCRYPTED_DIRECT_MESSAGE = 4
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										53
									
								
								lib/nostr/events/encrypted_direct_message.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								lib/nostr/events/encrypted_direct_message.rb
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,53 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module Nostr
 | 
			
		||||
  # Classes of event kinds.
 | 
			
		||||
  module Events
 | 
			
		||||
    # An event whose +content+ is encrypted. It can only be decrypted by the owner of the private key that pairs
 | 
			
		||||
    # the event's +pubkey+.
 | 
			
		||||
    class EncryptedDirectMessage < Event
 | 
			
		||||
      # Instantiates a new encrypted direct message
 | 
			
		||||
      #
 | 
			
		||||
      # @api public
 | 
			
		||||
      #
 | 
			
		||||
      # @example Instantiating a new encrypted direct message
 | 
			
		||||
      #   Nostr::Events::EncryptedDirectMessage.new(
 | 
			
		||||
      #    sender_private_key: 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460',
 | 
			
		||||
      #    recipient_public_key: '48df4af6e240ac5f7c5de89bf5941b249880be0e87d03685b178ccb1a315f52e',
 | 
			
		||||
      #    plain_text: 'Your feedback is appreciated, now pay $8',
 | 
			
		||||
      # )
 | 
			
		||||
      #
 | 
			
		||||
      # @example Instantiating a new encrypted direct message that references a previous direct message
 | 
			
		||||
      #   Nostr::Events::EncryptedDirectMessage.new(
 | 
			
		||||
      #    sender_private_key: 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460',
 | 
			
		||||
      #    recipient_public_key: '48df4af6e240ac5f7c5de89bf5941b249880be0e87d03685b178ccb1a315f52e',
 | 
			
		||||
      #    plain_text: 'Your feedback is appreciated, now pay $8',
 | 
			
		||||
      #    previous_direct_message: 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460'
 | 
			
		||||
      # )
 | 
			
		||||
      #
 | 
			
		||||
      # @param plain_text [String] The +content+ of the encrypted message.
 | 
			
		||||
      # @param sender_private_key [String] 32-bytes hex-encoded private key of the message's author.
 | 
			
		||||
      # @param recipient_public_key [String] 32-bytes hex-encoded public key of the recipient of the encrypted message.
 | 
			
		||||
      # @param previous_direct_message [String] 32-bytes hex-encoded id identifying the previous message in a
 | 
			
		||||
      # conversation or a message we are explicitly replying to (such that contextual, more organized conversations
 | 
			
		||||
      # may happen
 | 
			
		||||
      #
 | 
			
		||||
      def initialize(plain_text:, sender_private_key:, recipient_public_key:, previous_direct_message: nil)
 | 
			
		||||
        crypto = Crypto.new
 | 
			
		||||
        keygen = Keygen.new
 | 
			
		||||
 | 
			
		||||
        encrypted_content = crypto.encrypt_text(sender_private_key, recipient_public_key, plain_text)
 | 
			
		||||
        sender_public_key = keygen.extract_public_key(sender_private_key)
 | 
			
		||||
 | 
			
		||||
        super(
 | 
			
		||||
          pubkey: sender_public_key,
 | 
			
		||||
          kind: Nostr::EventKind::ENCRYPTED_DIRECT_MESSAGE,
 | 
			
		||||
          content: encrypted_content,
 | 
			
		||||
          )
 | 
			
		||||
 | 
			
		||||
        add_pubkey_reference(recipient_public_key)
 | 
			
		||||
        add_event_reference(previous_direct_message) if previous_direct_message
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,9 +1,16 @@
 | 
			
		||||
module Nostr
 | 
			
		||||
  class Crypto
 | 
			
		||||
    BN_BASE: Integer
 | 
			
		||||
    CIPHER_CURVE: String
 | 
			
		||||
    CIPHER_ALGORITHM: String
 | 
			
		||||
 | 
			
		||||
    def encrypt_text: (String, String, String) -> String
 | 
			
		||||
    def decrypt_text: (String, String, String) -> String
 | 
			
		||||
    def sign_event: (Event, String) -> Event
 | 
			
		||||
 | 
			
		||||
    private
 | 
			
		||||
 | 
			
		||||
    def compute_shared_key: (String, String) -> String
 | 
			
		||||
    def hash_event:(Event) -> String
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@ -4,5 +4,6 @@ module Nostr
 | 
			
		||||
    TEXT_NOTE: Integer
 | 
			
		||||
    RECOMMEND_SERVER: Integer
 | 
			
		||||
    CONTACT_LIST: Integer
 | 
			
		||||
    ENCRYPTED_DIRECT_MESSAGE: Integer
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										12
									
								
								sig/nostr/events/encrypted_direct_message.rbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								sig/nostr/events/encrypted_direct_message.rbs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
			
		||||
module Nostr
 | 
			
		||||
  module Events
 | 
			
		||||
    class EncryptedDirectMessage < Event
 | 
			
		||||
      def initialize: (
 | 
			
		||||
          plain_text: String,
 | 
			
		||||
          sender_private_key: String,
 | 
			
		||||
          recipient_public_key: String,
 | 
			
		||||
          ?previous_direct_message: String|nil
 | 
			
		||||
        ) -> void
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -30,4 +30,50 @@ RSpec.describe Nostr::Crypto do
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe '#encrypt_text' do
 | 
			
		||||
    let(:sender_keypair) do
 | 
			
		||||
      Nostr::KeyPair.new(
 | 
			
		||||
        public_key: '8a9d69c56e3c691bec8f9565e4dcbe38ae1d88fffeec3ce66b9f47558a3aa8ca',
 | 
			
		||||
        private_key: '3185a47e3802f956ca5a2b4ea606c1d51c7610f239617e8f0f218d55bdf2b757'
 | 
			
		||||
      )
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    let(:recipient_keypair) do
 | 
			
		||||
      Nostr::KeyPair.new(
 | 
			
		||||
        public_key: '6c31422248998e300a1a457167565da7d15d0da96651296ee2791c29c11b6aa0',
 | 
			
		||||
        private_key: '22cea01c33eccf30fdd54cb6728f814f6de00c778aafd721e017f4582545f9cf'
 | 
			
		||||
      )
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'encrypts plain text' do
 | 
			
		||||
      encrypted_text = crypto.encrypt_text(sender_keypair.private_key, recipient_keypair.public_key, 'Twitter Files')
 | 
			
		||||
      decrypted_text = crypto.decrypt_text(recipient_keypair.private_key, sender_keypair.public_key, encrypted_text)
 | 
			
		||||
 | 
			
		||||
      expect(decrypted_text).to eq('Twitter Files')
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe '#descrypt_text' do
 | 
			
		||||
    let(:sender_keypair) do
 | 
			
		||||
      Nostr::KeyPair.new(
 | 
			
		||||
        public_key: '8a9d69c56e3c691bec8f9565e4dcbe38ae1d88fffeec3ce66b9f47558a3aa8ca',
 | 
			
		||||
        private_key: '3185a47e3802f956ca5a2b4ea606c1d51c7610f239617e8f0f218d55bdf2b757'
 | 
			
		||||
      )
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    let(:recipient_keypair) do
 | 
			
		||||
      Nostr::KeyPair.new(
 | 
			
		||||
        public_key: '6c31422248998e300a1a457167565da7d15d0da96651296ee2791c29c11b6aa0',
 | 
			
		||||
        private_key: '22cea01c33eccf30fdd54cb6728f814f6de00c778aafd721e017f4582545f9cf'
 | 
			
		||||
      )
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'decrypts an encrypted text' do
 | 
			
		||||
      encrypted_text = crypto.encrypt_text(sender_keypair.private_key, recipient_keypair.public_key, 'Twitter Files')
 | 
			
		||||
      decrypted_text = crypto.decrypt_text(recipient_keypair.private_key, sender_keypair.public_key, encrypted_text)
 | 
			
		||||
 | 
			
		||||
      expect(decrypted_text).to eq('Twitter Files')
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@ -26,4 +26,10 @@ RSpec.describe Nostr::EventKind do
 | 
			
		||||
      expect(described_class::CONTACT_LIST).to eq(3)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe '::ENCRYPTED_DIRECT_MESSAGE' do
 | 
			
		||||
    it 'is an integer' do
 | 
			
		||||
      expect(described_class::ENCRYPTED_DIRECT_MESSAGE).to eq(4)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										84
									
								
								spec/nostr/events/encrypted_direct_message_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								spec/nostr/events/encrypted_direct_message_spec.rb
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,84 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'spec_helper'
 | 
			
		||||
 | 
			
		||||
RSpec.describe Nostr::Events::EncryptedDirectMessage do
 | 
			
		||||
  describe '.new' do
 | 
			
		||||
    let(:sender_keypair) do
 | 
			
		||||
      Nostr::KeyPair.new(
 | 
			
		||||
        public_key: '8a9d69c56e3c691bec8f9565e4dcbe38ae1d88fffeec3ce66b9f47558a3aa8ca',
 | 
			
		||||
        private_key: '3185a47e3802f956ca5a2b4ea606c1d51c7610f239617e8f0f218d55bdf2b757'
 | 
			
		||||
      )
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    let(:recipient_keypair) do
 | 
			
		||||
      Nostr::KeyPair.new(
 | 
			
		||||
        public_key: '6c31422248998e300a1a457167565da7d15d0da96651296ee2791c29c11b6aa0',
 | 
			
		||||
        private_key: '22cea01c33eccf30fdd54cb6728f814f6de00c778aafd721e017f4582545f9cf'
 | 
			
		||||
      )
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    let(:crypto) { Nostr::Crypto.new }
 | 
			
		||||
 | 
			
		||||
    it 'creates an instance of an encrypted private message event that can be decrypted by its recipient' do
 | 
			
		||||
      encrypted_direct_message = described_class.new(
 | 
			
		||||
        sender_private_key: sender_keypair.private_key,
 | 
			
		||||
        recipient_public_key: recipient_keypair.public_key,
 | 
			
		||||
        plain_text: 'Your feedback is appreciated, now pay $8'
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
      plain_text = crypto.decrypt_text(
 | 
			
		||||
        recipient_keypair.private_key,
 | 
			
		||||
        sender_keypair.public_key,
 | 
			
		||||
        encrypted_direct_message.content
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
      aggregate_failures do
 | 
			
		||||
        expect(encrypted_direct_message.content).not_to eq(plain_text)
 | 
			
		||||
        expect(plain_text).to eq('Your feedback is appreciated, now pay $8')
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'adds a reference to the recipient in the tags' do
 | 
			
		||||
      encrypted_direct_message = described_class.new(
 | 
			
		||||
        sender_private_key: sender_keypair.private_key,
 | 
			
		||||
        recipient_public_key: recipient_keypair.public_key,
 | 
			
		||||
        plain_text: 'Your feedback is appreciated, now pay $8'
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
      expect(encrypted_direct_message.tags).to eq([['p', recipient_keypair.public_key]])
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'when previous_direct_message is omitted' do
 | 
			
		||||
      it 'does not add a reference to a previous message' do
 | 
			
		||||
        encrypted_direct_message = described_class.new(
 | 
			
		||||
          sender_private_key: sender_keypair.private_key,
 | 
			
		||||
          recipient_public_key: recipient_keypair.public_key,
 | 
			
		||||
          plain_text: 'Your feedback is appreciated, now pay $8'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        expect(encrypted_direct_message.tags).to eq([['p', recipient_keypair.public_key]])
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'when previous_direct_message is given' do
 | 
			
		||||
      let(:previous_direct_message_id) { 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460' }
 | 
			
		||||
 | 
			
		||||
      it 'adds a reference to a previous message' do
 | 
			
		||||
        encrypted_direct_message = described_class.new(
 | 
			
		||||
          sender_private_key: sender_keypair.private_key,
 | 
			
		||||
          recipient_public_key: recipient_keypair.public_key,
 | 
			
		||||
          plain_text: 'Your feedback is appreciated, now pay $8',
 | 
			
		||||
          previous_direct_message: previous_direct_message_id
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        expect(encrypted_direct_message.tags).to eq(
 | 
			
		||||
          [
 | 
			
		||||
            ['p', recipient_keypair.public_key],
 | 
			
		||||
            ['e', previous_direct_message_id]
 | 
			
		||||
          ]
 | 
			
		||||
        )
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user