lib/eth/key/encrypter.rb



require 'json'
require 'securerandom'

class Eth::Key::Encrypter
  include Eth::Utils

  def self.perform(key, password, options = {})
    new(key, options).perform(password)
  end

  def initialize(key, options = {})
    @key = key
    @options = options
  end

  def perform(password)
    derive_key password
    encrypt

    data.to_json
  end

  def data
    {
      crypto: {
        cipher: cipher_name,
        cipherparams: {
          iv: bin_to_hex(iv),
        },
        ciphertext: bin_to_hex(encrypted_key),
        kdf: "pbkdf2",
        kdfparams: {
          c: iterations,
          dklen: 32,
          prf: prf,
          salt: bin_to_hex(salt),
        },
        mac: bin_to_hex(mac),
      },
      id: id,
      version: 3,
    }.tap do |data|
      data[:address] = address unless options[:skip_address]
    end
  end

  def id
    @id ||= options[:id] || SecureRandom.uuid
  end


  private

  attr_reader :derived_key, :encrypted_key, :key, :options

  def cipher
    @cipher ||= OpenSSL::Cipher.new(cipher_name).tap do |cipher|
      cipher.encrypt
      cipher.iv = iv
      cipher.key = derived_key[0, (key_length/2)]
    end
  end

  def digest
    @digest ||= OpenSSL::Digest.new digest_name
  end

  def derive_key(password)
    @derived_key = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, key_length, digest)
  end

  def encrypt
    @encrypted_key = cipher.update(hex_to_bin key) + cipher.final
  end

  def mac
    keccak256(derived_key[(key_length/2), key_length] + encrypted_key)
  end

  def cipher_name
    "aes-128-ctr"
  end

  def digest_name
    "sha256"
  end

  def prf
    "hmac-#{digest_name}"
  end

  def key_length
    32
  end

  def salt_length
    32
  end

  def iv_length
    16
  end

  def iterations
    options[:iterations] || 262_144
  end

  def salt
    @salt ||= if options[:salt]
      hex_to_bin options[:salt]
    else
      SecureRandom.random_bytes(salt_length)
    end
  end

  def iv
    @iv ||= if options[:iv]
      hex_to_bin options[:iv]
    else
      SecureRandom.random_bytes(iv_length)
    end
  end

  def address
    Eth::Key.new(priv: key).address
  end

end