# encoding: binary
# frozen_string_literal: true

require "forwardable"

# NaCl/libsodium for Ruby
module RbNaCl
  # The simplest nonce strategy that could possibly work
  #
  # This class implements the simplest possible nonce generation strategy to
  # wrap a RbNaCl::Box or RbNaCl::SecretBox.  A 24-byte random nonce is used
  # for the encryption and is prepended to the message.  When it is time to
  # open the box, the message is split into nonce and ciphertext, and then the
  # box is decrypted.
  #
  # Thanks to the size of the nonce, the chance of a collision is negligible.  For
  # example, after encrypting 2^64 messages, the odds of their having been
  # repeated nonce is approximately 2^-64.  As an additional convenience, the
  # ciphertexts may be encoded or decoded by any of the encoders implemented in
  # the library.
  #
  # The resulting ciphertexts are 40 bytes longer than the plain text (24 byte
  # nonce plus a 16 byte authenticator).  This might be annoying if you're
  # encrypting tweets, but for files represents a fairly small overhead.
  #
  # Some caveats:
  #
  # * If your random source is broken, so is the security of the messages.  You
  #   have bigger problems than just this library at that point, but it's worth
  #   saying.
  # * The confidentiality of your messages is assured with this strategy, but
  #   there is no protection against messages being reordered and replayed by an
  #   active adversary.
  class SimpleBox
    extend Forwardable
    def_delegators :@box, :nonce_bytes, :primitive

    # Create a new SimpleBox
    #
    # @param box [SecretBox, Box] the SecretBox or Box to use.
    #
    # @return [SimpleBox] Ready for use
    def initialize(box)
      @box = box
    end

    # Use a secret key to create a SimpleBox
    #
    # This is a convenience method.  It takes a secret key and instantiates a
    # SecretBox under the hood, then returns the new SimpleBox.
    #
    # @param secret_key [String] The secret key, 32 bytes long.
    #
    # @return [SimpleBox] Ready for use
    def self.from_secret_key(secret_key)
      new(SecretBox.new(secret_key))
    end

    # Use a pair of keys to create a SimpleBox
    #
    # This is a convenience method.  It takes a pair of keys and instantiates a
    # Box under the hood, then returns the new SimpleBox.
    #
    # @param public_key  [PublicKey,  String] The RbNaCl public key, as class or string
    # @param private_key [PrivateKey, String] The RbNaCl private key, as class or string
    #
    # @return [SimpleBox] Ready for use
    def self.from_keypair(public_key, private_key)
      new(Box.new(public_key, private_key))
    end

    # Encrypts the message with a random nonce
    #
    # Encrypts the message with a random nonce, then returns the ciphertext with
    # the nonce prepended.  Optionally encodes the message using an encoder.
    #
    # @param message [String] The message to encrypt
    #
    # @return [String] The enciphered message
    def box(message)
      nonce = generate_nonce
      cipher_text = @box.box(nonce, message)
      nonce + cipher_text
    end
    alias encrypt box

    # Decrypts the ciphertext with a random nonce
    #
    # Takes a ciphertext, optionally decodes it, then splits the nonce off the
    # front and uses this to decrypt.  Returns the message.
    #
    # @param enciphered_message [String] The message to decrypt.
    #
    # @raise [CryptoError] If the message has been tampered with.
    #
    # @return [String] The decoded message
    def open(enciphered_message)
      nonce, ciphertext = extract_nonce(enciphered_message.to_s)
      @box.open(nonce, ciphertext)
    end
    alias decrypt open

    private

    def generate_nonce
      Random.random_bytes(nonce_bytes)
    end

    def extract_nonce(bytes)
      nonce = bytes.slice(0, nonce_bytes)
      [nonce, bytes.slice(nonce_bytes..-1)]
    end
  end

  # Backwards compatibility with the old RandomNonceBox name
  RandomNonceBox = SimpleBox
end
