diff --git a/README.md b/README.md index ff6c163..c39c744 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,10 @@ AionCLI is a collection of scripts mainly for handling csv files. ## Installation -Make sure you have rbenv installed. This can be done through homebrew. +This project has been modernized for current Ruby and is intended to be Ruby 4-ready. +At the moment it is verified in this repository with Ruby 3.2.x and updated gems that are compatible with newer Ruby/OpenSSL behavior. + +Make sure you have `rbenv` installed. This can be done through Homebrew. Follow the instructions on how to setup homebrew: https://brew.sh/index_da @@ -16,26 +19,26 @@ After this, install rbenv using the following command: Follow the instructions on how to setup rbenv: https://github.com/rbenv/rbenv#homebrew-on-macos -Install a ruby version to use: +Install a recent Ruby version to use: - $ rbenv install 2.5.3 + $ rbenv install 3.2.2 Clone the repository to some folder, and navigate to it: $ git clone https://github.com/aion-dk/aion_cli.git $ cd aion_cli -Specify an installed ruby version to use: +Specify an installed Ruby version to use: - $ rbenv local 2.5.3 + $ rbenv local 3.2.2 -Install bundler, pull gems and install the aion script: +Install Bundler, install gems, and install the `aion` script: $ gem install bundler $ bundle install $ bundle exec aion install -You should be ready to go :-) +You should now be ready to go. ## Usage diff --git a/aion_cli.gemspec b/aion_cli.gemspec index 6d1740f..73f1eb5 100644 --- a/aion_cli.gemspec +++ b/aion_cli.gemspec @@ -1,5 +1,4 @@ -# coding: utf-8 -lib = File.expand_path('../lib', __FILE__) +lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'aion_cli/version' @@ -9,11 +8,12 @@ Gem::Specification.new do |spec| spec.authors = ['Michael Andersen'] spec.email = ['michael.andersen.85@gmail.com'] - spec.summary = %q{A small collection of scripts used by Aion Aps} - spec.description = %q{A small collection of scripts used by Aion Aps. Mainly for handling csv files.} + spec.summary = 'A small collection of scripts used by Aion Aps' + spec.description = 'A small collection of scripts used by Aion Aps. Mainly for handling csv files.' spec.homepage = 'https://github.com/aion-dk/aion_cli' spec.license = 'Aion' + spec.required_ruby_version = '>= 3.2' # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host' # to allow pushing to a single host or delete this section to allow pushing to any host. @@ -28,16 +28,15 @@ Gem::Specification.new do |spec| spec.require_paths = ['lib'] - spec.add_dependency 'activesupport', '~> 5.2.1' + spec.add_dependency 'activesupport', '~> 7.2' spec.add_dependency 'aion-s3', '~> 1.0' spec.add_dependency 'charlock_holmes', '~> 0.7.6' - spec.add_dependency 'faker', '~> 1.9.1' + spec.add_dependency 'faker', '~> 3.6' spec.add_dependency 'http', '~> 5.0.4' - spec.add_dependency 'roo', '~> 2.7.1' - spec.add_dependency 'thor', '~> 0.20.3' - spec.add_dependency 'write_xlsx', '~> 0.85.5' + spec.add_dependency 'roo', '~> 3.0' + spec.add_dependency 'thor', '~> 1.4' + spec.add_dependency 'write_xlsx', '~> 1.15' - #spec.add_development_dependency 'bundler', '~> 1.17.1' - #spec.add_development_dependency 'rake', '~> 12.3.1' - #spec.add_development_dependency 'minitest', '~> 5.11.3' + spec.add_development_dependency 'minitest', '~> 5.27' + spec.add_development_dependency 'rake', '~> 13.3' end diff --git a/lib/aion_cli/commands/add.rb b/lib/aion_cli/commands/add.rb index 5051c3e..e283b5a 100644 --- a/lib/aion_cli/commands/add.rb +++ b/lib/aion_cli/commands/add.rb @@ -1,4 +1,5 @@ require 'securerandom' +require 'date' require 'set' require 'aion_cli/helpers/application_helper' require 'aion_cli/helpers/unique_string_generator' @@ -52,6 +53,8 @@ def unique_numbers(path) min = ask_natural_number('Pick the minimum') max = ask_natural_number('Pick the maximum') + raise Thor::Error, 'Maximum must be greater than minimum' unless max > min + generator = UniqueStringGenerator.new do (min + SecureRandom.random_number(max - min)).to_s end @@ -67,12 +70,7 @@ def unique_numbers(path) desc 'age CSV_FILE', 'Add age calculated from CPR birthdate to selected date. DATE defaults to "Today"' def age(path) date_input = ask_date_string('Input date to calculate age from. (Date defaults "Today" if blank)', true) - date = nil - if date_input == "" - date = Date.today - else - date = Date.parse(date_input) - end + date = date_input.empty? ? Date.today : Date.parse(date_input) headers, *rows = read_spreadsheet(path) index_cpr = ask_header_index(headers, 'Specify CPR column.') diff --git a/lib/aion_cli/commands/avx.rb b/lib/aion_cli/commands/avx.rb index 4383104..4e2d874 100644 --- a/lib/aion_cli/commands/avx.rb +++ b/lib/aion_cli/commands/avx.rb @@ -109,7 +109,7 @@ def credentials_aggregate(*paths) paths_rest.each do |path| - headers, *rows = read_csv(path) + _headers, *rows = read_csv(path) rows.each do |row| id = row[index] diff --git a/lib/aion_cli/commands/data.rb b/lib/aion_cli/commands/data.rb index 34555c6..3bb6849 100644 --- a/lib/aion_cli/commands/data.rb +++ b/lib/aion_cli/commands/data.rb @@ -77,9 +77,10 @@ def stats_3f(path, json_path) index_department = ask_header_index(headers, 'Specify the column with the DEPARTMENT NUMBER') index_profession = ask_header_index(headers, 'Specify the column with the PROFESSION NUMBER') index_overall_groups = ask_header_index(headers, 'Specify the column with the GROUP MAPPING') + voting_round_reference = ask("Specify the VOTING ROUNDS REFERENCE to use for voter totals from the eligible voters file (ex. 'voting_round_1'): ") - file = File.read(json_path) - total_json = JSON.parse(file).first.last # Value of first voting round + json_file = JSON.parse(File.read(json_path)) + total_json = json_file.fetch(voting_round_reference, json_file.first.last) index_option_label = get_column_index(headers, 'option_label') diff --git a/lib/aion_cli/commands/excel.rb b/lib/aion_cli/commands/excel.rb index bf87839..c767b5f 100644 --- a/lib/aion_cli/commands/excel.rb +++ b/lib/aion_cli/commands/excel.rb @@ -6,6 +6,7 @@ module AionCLI module CLI class Excel < Thor + include AionCLI::ApplicationHelper desc 'to_csv', 'Convert Excel file to CSV' long_desc <<-LONG_DESC @@ -13,13 +14,13 @@ class Excel < Thor With rejection of top and left padding cells. LONG_DESC - option :sheet, aliasses: '-s', banner: 'Sheet number' + option :sheet, aliases: '-s', banner: 'Sheet number' def to_csv(path) absolute_path = File.absolute_path(path) xlsx = Roo::Spreadsheet.open(absolute_path) - unless options[:s].nil? - sheet_number = options[:s].to_i + unless options[:sheet].nil? + sheet_number = options[:sheet].to_i xlsx.default_sheet = xlsx.sheets[sheet_number] end diff --git a/lib/aion_cli/commands/table.rb b/lib/aion_cli/commands/table.rb index 7e09556..cf7ccdf 100644 --- a/lib/aion_cli/commands/table.rb +++ b/lib/aion_cli/commands/table.rb @@ -116,7 +116,7 @@ def join(path_a, path_b) next end - raise Thor::Error, "Duplicate keys in join column for ADDITIONAL_FILE (key: #{key})" if join_b_hash.has_key?(key) + raise Thor::Error, "Duplicate keys in join column for ADDITIONAL_FILE (key: #{key})" if join_b_hash.key?(key) join_b_hash[key] = values end @@ -182,7 +182,7 @@ def vlookup(path_a, path_b) raise Thor::Error, 'Empty value in join column for ADDITIONAL_FILE' unless allow_empty end - raise Thor::Error, "Duplicate key combination in join columns for ADDITIONAL_FILE (key: #{key})" if join_b_hash.has_key?(key) + raise Thor::Error, "Duplicate key combination in join columns for ADDITIONAL_FILE (key: #{key})" if join_b_hash.key?(key) join_b_hash[key] = row[lookup_index_b] end diff --git a/lib/aion_cli/commands/text.rb b/lib/aion_cli/commands/text.rb index 8b37b5a..f42556d 100644 --- a/lib/aion_cli/commands/text.rb +++ b/lib/aion_cli/commands/text.rb @@ -1,5 +1,6 @@ require 'aion_cli/helpers/application_helper' require 'aion_cli/helpers/cpr_parser' +require 'csv' module AionCLI module CLI @@ -15,7 +16,7 @@ def to_csv(path, column_widths = nil) - if column_widths and column_widths =~ /^\d+(,\d+)*$/ + if column_widths && column_widths.match?(/^\d+(,\d+)*$/) _column_widths = column_widths.split(',').map(&:to_i) absolute_path = File.absolute_path(path) @@ -65,7 +66,7 @@ def filter_multi_lines(path) begin start_regex = Regexp.new(ask("Specify the regex to start line extract")) say "Interpreted as: #{start_regex.inspect}", :green - rescue + rescue RegexpError start_regex = "" say "Could not parse regex", :yellow end @@ -74,7 +75,7 @@ def filter_multi_lines(path) begin stop_regex = Regexp.new(ask("Specify the regex to end line extract")) say "Interpreted as: #{stop_regex.inspect}", :green - rescue + rescue RegexpError stop_regex = "" say "Could not parse regex", :yellow end @@ -140,7 +141,7 @@ def cpr_to_csv(path) ask_output do |csv| csv << ['cpr'] + selected_attributes.map(&:to_s) records.each do |cpr_no, attributes| - csv << [cpr_no] + selected_attributes.map{ |attr_name| attributes[attr_name].try(:strip) } + csv << [cpr_no] + selected_attributes.map { |attr_name| attributes[attr_name]&.strip } end end end diff --git a/lib/aion_cli/helpers/application_helper.rb b/lib/aion_cli/helpers/application_helper.rb index 3c678cc..12f19e7 100644 --- a/lib/aion_cli/helpers/application_helper.rb +++ b/lib/aion_cli/helpers/application_helper.rb @@ -1,5 +1,7 @@ require 'csv' require 'charlock_holmes' +require 'fileutils' +require 'roo' module AionCLI module ApplicationHelper @@ -8,14 +10,14 @@ module ApplicationHelper def read_spreadsheet(path) absolute_path = File.expand_path(path) - case File.extname(path) + case File.extname(absolute_path) when '.csv' table = read_csv(path) when '.xlsx' size = File.size(absolute_path) size_mb = (size / 1024.0 / 1024.0).round if size_mb > 5 - say("The XSLT file #{absolute_path} is ~ #{size_mb}MB.") + say("The XLSX file #{absolute_path} is ~ #{size_mb}MB.") say('To improve the speed, consider converting the file to a csv.') unless yes?('Would you like to continue?', :yellow) say('Done', :green) @@ -41,14 +43,15 @@ def read_csv(path, options = {}) # Remove BOM from contents content.sub!("\xEF\xBB\xBF", '') options[:col_sep] ||= detect_col_sep(content) - CSV.parse(content, options) + CSV.parse(content, **options) end # CSV Helpers def read_file(path) content = File.read(path) detection = CharlockHolmes::EncodingDetector.detect(content) - CharlockHolmes::Converter.convert(content, detection[:encoding], 'UTF-8') + source_encoding = detection&.dig(:encoding) || 'UTF-8' + CharlockHolmes::Converter.convert(content, source_encoding, 'UTF-8') end def ask_header_index(headers, message) @@ -150,7 +153,7 @@ def ask_date_string(message, allow_blank = false) date = ask("Date (YYYY-MM-DD):") - unless allow_blank || date.match(/^\d{4}\-\d{2}\-\d{2}$/) + unless allow_blank || date.match?(/^\d{4}-\d{2}-\d{2}$/) say("'#{date}' is invalid. Date must have this format -> YYYY-MM-DD", :red) next end @@ -177,7 +180,7 @@ def ask_output_path(extname = '.csv', default_name = 'out', message = 'Pick a na absolute_output_path = File.expand_path(output_path) - if File.exists?(absolute_output_path) + if File.exist?(absolute_output_path) if yes?('The file already exists. Would you like to overwrite?', :yellow) File.unlink(absolute_output_path) else @@ -196,7 +199,7 @@ def ask_output_dir(default_name = 'out', mkdir: false) absolute_output_dir = File.expand_path(output_dir) - if File.exists?(absolute_output_dir) + if File.exist?(absolute_output_dir) say("Already exists: #{absolute_output_dir}", :red) next end @@ -225,25 +228,20 @@ def ask_unpack_path(source_path) end def get_column_index(headers, column_name) - headers.each_with_index do |header, index| - if header == column_name - return index - end - end - + headers.index(column_name) end private def detect_col_sep(contents) - test_contents = contents.lines.first.chomp - test_results = [',',';',"\t"].map { |col_sep| [count_col_sep(test_contents, col_sep), col_sep ] } + test_contents = contents.lines.first.to_s.chomp + test_results = [',', ';', "\t"].map { |col_sep| [count_col_sep(test_contents, col_sep), col_sep] } test_results.sort.last.last end def count_col_sep(test_contents, col_sep) - CSV.parse(test_contents, col_sep: col_sep).first.size - rescue + CSV.parse(test_contents, col_sep: col_sep).first&.size.to_i + rescue StandardError 0 end diff --git a/lib/aion_cli/helpers/cpr_parser.rb b/lib/aion_cli/helpers/cpr_parser.rb index 06f1719..e70ad6b 100644 --- a/lib/aion_cli/helpers/cpr_parser.rb +++ b/lib/aion_cli/helpers/cpr_parser.rb @@ -1,3 +1,5 @@ +require 'set' + class CPRParser SUPPORTED_RECORD_TYPES = [ '001', # Personoplysninger diff --git a/lib/aion_cli/helpers/dawa_client.rb b/lib/aion_cli/helpers/dawa_client.rb index fdf1c02..cba9fb8 100644 --- a/lib/aion_cli/helpers/dawa_client.rb +++ b/lib/aion_cli/helpers/dawa_client.rb @@ -1,6 +1,7 @@ require 'singleton' require 'http' require 'json' +require 'stringio' module AionCLI class DAWAClient @@ -76,8 +77,8 @@ def municipalities # @return a list of names # @raise StandardError if a name was not found def municipality_names(codes) - pairs = Hash[ municipalities.map { |o| [o['kode'].to_i, o['navn']] } ] - codes.map { |code| pairs[code] or raise 'Name not found' } + pairs = municipalities.to_h { |municipality| [municipality['kode'].to_i, municipality['navn']] } + codes.map { |code| pairs.fetch(code) { raise 'Name not found' } } end @@ -88,7 +89,7 @@ def request(url, params: {}, max_retries: 3) return JSON.parse(dawa_response) rescue JSON::ParserError => e $stderr << "Error occured when attempting to parse response from DAWA: #{e.message}\nRetrying...\n" - rescue => e + rescue StandardError => e $stderr << "Error occured: #{e.message}\nRetrying...\n" end diff --git a/lib/aion_cli/helpers/preparation_helper.rb b/lib/aion_cli/helpers/preparation_helper.rb index 07ac1cf..cc12489 100644 --- a/lib/aion_cli/helpers/preparation_helper.rb +++ b/lib/aion_cli/helpers/preparation_helper.rb @@ -1,4 +1,7 @@ +require 'date' +require 'uri' + module AionCLI module PreparationHelper @@ -39,14 +42,16 @@ def clean_phone(phone) end def clean_dob(date,format) - begin Date.strptime(date, format).strftime('%d%m%y') rescue nil end + Date.strptime(date, format).strftime('%d%m%y') + rescue ArgumentError + nil end def slice_file(rows, indexes, prefix, suffix) file_name = "#{prefix}_#{suffix}.csv" output_path = File.expand_path(file_name) - if File.exists?(output_path) + if File.exist?(output_path) if yes?("The file #{file_name} already exists. Would you like to overwrite?", :yellow) File.unlink(output_path) else diff --git a/lib/aion_cli/helpers/unique_string_generator.rb b/lib/aion_cli/helpers/unique_string_generator.rb index d2422f0..537ce4f 100644 --- a/lib/aion_cli/helpers/unique_string_generator.rb +++ b/lib/aion_cli/helpers/unique_string_generator.rb @@ -1,3 +1,5 @@ +require 'set' + class UniqueStringGenerator def initialize(used_values = [], &block) diff --git a/lib/aion_cli/main.rb b/lib/aion_cli/main.rb index 1157b71..c6ac46e 100644 --- a/lib/aion_cli/main.rb +++ b/lib/aion_cli/main.rb @@ -1,4 +1,5 @@ require 'thor' +require 'shellwords' require 'aion_cli/commands/avx' require 'aion_cli/commands/dawa' require 'aion_cli/commands/excel' @@ -12,41 +13,45 @@ module AionCLI class Main < Thor + PROJECT_ROOT = File.expand_path('../..', __dir__) desc 'update', 'Pull the latest changes' def update - project_root = File.expand_path(File.join(__FILE__, '../../../')) - system("cd #{project_root} && git pull --ff && bundle install") + Dir.chdir(PROJECT_ROOT) do + system('git', 'pull', '--ff') && system('bundle', 'install') + end end desc 'install', 'Install an executable at /usr/local/bin/aion' def install - project_root = File.expand_path(File.join(__FILE__, '../../../')) - - version_file = %x[rbenv version-file #{project_root}].chomp + version_file = %x[rbenv version-file #{PROJECT_ROOT.shellescape}].chomp ruby_version = %x[rbenv version-file-read #{version_file}].chomp if version_file.present? ruby_version ||= %x[rbenv version-name].chomp - script_contents = <<-EOS.gsub(/^\s{8}/,'') + escaped_project_root = PROJECT_ROOT.shellescape + escaped_ruby_version = ruby_version.shellescape + + script_contents = <<~EOS #!/usr/bin/env bash + set -e # Switch to ruby #{ruby_version} - eval "$(rbenv init -)" - rbenv shell #{ruby_version} + eval "$(rbenv init - bash)" + rbenv shell #{escaped_ruby_version} # Trigger script - BUNDLE_GEMFILE=#{project_root}/Gemfile bundle exec #{project_root}/bin/aion "$@" + exec env BUNDLE_GEMFILE=#{escaped_project_root}/Gemfile bundle exec #{escaped_project_root}/bin/aion "$@" EOS path = '/usr/local/bin/aion' - if File.exists?(path) + if File.exist?(path) say("#{path} already exists") return if no?('Do you want to overwrite?') end File.write(path, script_contents) - File.chmod(0755, path) + File.chmod(0o755, path) say("aion script installed into path #{path}") end diff --git a/lib/crypto/crypto.rb b/lib/crypto/crypto.rb index c03c314..d772a42 100644 --- a/lib/crypto/crypto.rb +++ b/lib/crypto/crypto.rb @@ -1,5 +1,11 @@ require 'openssl' require 'securerandom' +require 'digest' +require 'crypto/discrete_logarithm_equality_proof' +require 'crypto/discrete_logarithm_multiple_proof' +require 'crypto/discrete_logarithm_proof' +require 'crypto/el_gamal_point_cryptogram' +require 'crypto/el_gamal_scalar_cryptogram' require 'crypto/schnorr_signature' module Crypto @@ -21,7 +27,7 @@ def verify_schnorr_signature(message, signature_string, public_key_string) public_key = hex_to_point public_key_string schnorr_signature.verify(message, public_key) - rescue => _ + rescue StandardError # In case of any error (fx. malformed parameters) we return false false end @@ -42,7 +48,7 @@ def generate_schnorr_signature(message, private_key_string) # Generates a key pair as voter credential # # @return two Strings the private key and the public key - def generate_key_pair() + def generate_key_pair private_key = random_bn(CURVE.group.order) public_key = CURVE.group.generator.mul(private_key) @@ -328,7 +334,7 @@ def encode_vote_to_point(vote_encode_type, vote) begin point = OpenSSL::PKey::EC::Point.new(CURVE.group, point_bn) found = true if point.on_curve? - rescue + rescue StandardError point_bn = point_bn + OpenSSL::BN.new(1) end until found @@ -343,7 +349,7 @@ def encode_vote_to_point(vote_encode_type, vote) begin point = OpenSSL::PKey::EC::Point.new(CURVE.group, point_bn) found = true if point.on_curve? - rescue + rescue StandardError point_bn = point_bn + OpenSSL::BN.new(1) end until found @@ -371,7 +377,7 @@ def random_point point_bn = OpenSSL::BN.new(point_encoding, 16) point = OpenSSL::PKey::EC::Point.new(CURVE.group, point_bn) found = point.on_curve? - rescue + rescue StandardError end until found point @@ -383,7 +389,7 @@ def random_point # @param point_2 (OpenSSL::PKey::EC::Point) The second point # @return (OpenSSL::PKey::EC::Point) the resulted point def add_points(point_1, point_2) - point_1.mul([OpenSSL::BN.new(1), OpenSSL::BN.new(1)], [point_2]) + point_1.add(point_2) end # Generates a random bn @@ -399,7 +405,7 @@ def random_bn(max = CURVE.group.order) # @param point_string String the hex string # @return (OpenSSL::PKey::EC::Point) the point def hex_to_point(point_string) - if point_string == '00' or point_string == '0' or point_string == '' + if point_string == '00' || point_string == '0' || point_string == '' # point = infinity_point point = OpenSSL::PKey::EC::Point.new(CURVE.group) else @@ -497,7 +503,7 @@ def homomorphically_add_cryptograms(c1, c2) # @param empty_cryptogram_string String with the empty cryptogram # @param encryption_key_string String with the encryption key # @return a the final vote cryptogram and value of the randomizer - def encrypt_vote (vote_string, empty_cryptogram_string, encryption_key_string) + def encrypt_vote(vote_string, empty_cryptogram_string, encryption_key_string) vote = hex_to_point vote_string empty_cryptogram = ElGamalPointCryptogram.from_s empty_cryptogram_string encryption_key = hex_to_point encryption_key_string @@ -525,7 +531,7 @@ def random_bn_hex # # @param secret_string The private key of the proof as a string # @return the proof as a string - def generate_discrete_logarithm_proof (secret_string) + def generate_discrete_logarithm_proof(secret_string) secret = hex_to_bn(secret_string) proof = Crypto::DiscreteLogarithmProof.generate(CURVE.group.generator, secret) diff --git a/lib/crypto/discrete_logarithm_equality_proof.rb b/lib/crypto/discrete_logarithm_equality_proof.rb index dd85e97..e91e5f0 100644 --- a/lib/crypto/discrete_logarithm_equality_proof.rb +++ b/lib/crypto/discrete_logarithm_equality_proof.rb @@ -20,17 +20,17 @@ class DiscreteLogarithmEqualityProof def initialize(commitment_1, commitment_2, challenge, response) @commitment_1, @commitment_2, @challenge, @response = commitment_1, commitment_2, challenge, response - (commitment_1.is_a?(OpenSSL::PKey::EC::Point) && - commitment_1.on_curve?) or raise ArgumentError, 'first commitment is not a valid point.' + ((commitment_1.is_a?(OpenSSL::PKey::EC::Point) && + commitment_1.on_curve?) || raise(ArgumentError, 'first commitment is not a valid point.')) - (commitment_2.is_a?(OpenSSL::PKey::EC::Point) && - commitment_2.on_curve?) or raise ArgumentError, 'second commitment is not a valid point.' + ((commitment_2.is_a?(OpenSSL::PKey::EC::Point) && + commitment_2.on_curve?) || raise(ArgumentError, 'second commitment is not a valid point.')) - (challenge.is_a?(OpenSSL::BN) && - challenge == challenge % CURVE.group.order) or raise ArgumentError, 'challenge is not a valid integer.' + ((challenge.is_a?(OpenSSL::BN) && + challenge == challenge % CURVE.group.order) || raise(ArgumentError, 'challenge is not a valid integer.')) - (response.is_a?(OpenSSL::BN) && - response == response % CURVE.group.order) or raise ArgumentError, 'response is not a valid integer.' + ((response.is_a?(OpenSSL::BN) && + response == response % CURVE.group.order) || raise(ArgumentError, 'response is not a valid integer.')) end # Instantiates an {DiscreteLogarithmEqualityProof} from a given string @@ -115,13 +115,13 @@ def verify(generator_1, generator_2, public_key_1, public_key_2) # @param public_key_1 (OpenSSL::PKey::EC::Point) the value of the first public key. # @param public_key_2 (OpenSSL::PKey::EC::Point) the value of the second public key. def verify_without_challenge(generator_1, generator_2, public_key_1, public_key_2) - left_hand_side_1 = generator_1.mul(response) + left_hand_side_1 = generator_1.mul(@response) # right_hand_side_1 = commitment_1 * 1 + public_key_1 * challenge - right_hand_side_1 = @commitment_1.mul([OpenSSL::BN.new(1), @challenge], [public_key_1]) + right_hand_side_1 = @commitment_1.add(public_key_1.mul(@challenge)) - left_hand_side_2 = generator_2.mul(response) + left_hand_side_2 = generator_2.mul(@response) # right_hand_side_2 = commitment_2 * 1 + public_key_2 * challenge - right_hand_side_2 = @commitment_2.mul([OpenSSL::BN.new(1), @challenge], [public_key_2]) + right_hand_side_2 = @commitment_2.add(public_key_2.mul(@challenge)) left_hand_side_1 == right_hand_side_1 && left_hand_side_2 == right_hand_side_2 end @@ -129,7 +129,7 @@ def verify_without_challenge(generator_1, generator_2, public_key_1, public_key_ # Outputs the proof as a string with all values encoded as hex, concatenated by a comma # # @return (String) The encoded proof. - def to_s() + def to_s Crypto.point_to_hex(@commitment_1) + ',' + Crypto.point_to_hex(@commitment_2) + ',' + Crypto.bn_to_hex(@challenge) + ',' + Crypto.bn_to_hex(@response) end end diff --git a/lib/crypto/discrete_logarithm_multiple_proof.rb b/lib/crypto/discrete_logarithm_multiple_proof.rb index 436a35f..b06376a 100644 --- a/lib/crypto/discrete_logarithm_multiple_proof.rb +++ b/lib/crypto/discrete_logarithm_multiple_proof.rb @@ -18,14 +18,14 @@ class DiscreteLogarithmMultipleProof def initialize(commitment, challenge, response) @commitment, @challenge, @response = commitment, challenge, response - (commitment.is_a?(OpenSSL::PKey::EC::Point) && - commitment.on_curve?) or raise ArgumentError, 'commitment is not a valid point.' + ((commitment.is_a?(OpenSSL::PKey::EC::Point) && + commitment.on_curve?) || raise(ArgumentError, 'commitment is not a valid point.')) - (challenge.is_a?(OpenSSL::BN) && - challenge == challenge % CURVE.group.order) or raise ArgumentError, 'challenge is not a valid integer.' + ((challenge.is_a?(OpenSSL::BN) && + challenge == challenge % CURVE.group.order) || raise(ArgumentError, 'challenge is not a valid integer.')) - (response.is_a?(OpenSSL::BN) && - response == response % CURVE.group.order) or raise ArgumentError, 'response is not a valid integer.' + ((response.is_a?(OpenSSL::BN) && + response == response % CURVE.group.order) || raise(ArgumentError, 'response is not a valid integer.')) end # Instantiates an {DiscreteLogarithmMultipleProof} from a given string @@ -73,7 +73,7 @@ def self.generate(generators, private_key) commitment = generators[0] for i in 0..n-1 # commitment = commitment * 1 + generator[i+1] * z[i] - commitment = commitment.mul([OpenSSL::BN.new(1), z[i]], [generators[i+1]]) + commitment = commitment.add(generators[i + 1].mul(z[i])) end commitment = commitment.mul(commitment_scalar) @@ -115,7 +115,7 @@ def self.generate_commitment(generators, private_key) commitment = generators[0] for i in 0..n-1 # commitment = commitment * 1 + generator[i+1] * z[i] - commitment = commitment.mul([OpenSSL::BN.new(1), z[i]], [generators[i+1]]) + commitment = commitment.add(generators[i + 1].mul(z[i])) end commitment = commitment.mul(commitment_scalar) @@ -178,17 +178,17 @@ def verify_without_challenge(generators, public_keys) left_hand_side = generators[0] for i in 0..n-1 # left_hand_side = left_hand_side * 1 + generator[i+1] * z[i] - left_hand_side = left_hand_side.mul([OpenSSL::BN.new(1), z[i]], [generators[i+1]]) + left_hand_side = left_hand_side.add(generators[i + 1].mul(z[i])) end left_hand_side = left_hand_side.mul(@response) right_hand_side = public_keys[0] for i in 0..n-1 # right_hand_side = right_hand_side * 1 + public_keys[i+1] * z[i] - right_hand_side = right_hand_side.mul([OpenSSL::BN.new(1), z[i]], [public_keys[i+1]]) + right_hand_side = right_hand_side.add(public_keys[i + 1].mul(z[i])) end # right_hand_side = commitment * 1 + right_hand_side * challenge - right_hand_side = @commitment.mul([OpenSSL::BN.new(1), @challenge], [right_hand_side]) + right_hand_side = @commitment.add(right_hand_side.mul(@challenge)) left_hand_side == right_hand_side end @@ -196,7 +196,7 @@ def verify_without_challenge(generators, public_keys) # Outputs the proof as a string with all values encoded as hex, concatenated by a comma # # @return (String) The encoded proof. - def to_s() + def to_s Crypto.point_to_hex(@commitment) + ',' + Crypto.bn_to_hex(@challenge) + ',' + Crypto.bn_to_hex(@response) end end diff --git a/lib/crypto/discrete_logarithm_proof.rb b/lib/crypto/discrete_logarithm_proof.rb index 4d355e3..aa12c0a 100644 --- a/lib/crypto/discrete_logarithm_proof.rb +++ b/lib/crypto/discrete_logarithm_proof.rb @@ -16,14 +16,14 @@ class DiscreteLogarithmProof def initialize(commitment, challenge, response) @commitment, @challenge, @response = commitment, challenge, response - (commitment.is_a?(OpenSSL::PKey::EC::Point) && - commitment.on_curve?) or raise ArgumentError, 'commitment is not a valid point.' + ((commitment.is_a?(OpenSSL::PKey::EC::Point) && + commitment.on_curve?) || raise(ArgumentError, 'commitment is not a valid point.')) - (challenge.is_a?(OpenSSL::BN) && - challenge == challenge % CURVE.group.order) or raise ArgumentError, 'challenge is not a valid integer.' + ((challenge.is_a?(OpenSSL::BN) && + challenge == challenge % CURVE.group.order) || raise(ArgumentError, 'challenge is not a valid integer.')) - (response.is_a?(OpenSSL::BN) && - response == response % CURVE.group.order) or raise ArgumentError, 'response is not a valid integer.' + ((response.is_a?(OpenSSL::BN) && + response == response % CURVE.group.order) || raise(ArgumentError, 'response is not a valid integer.')) end # Instantiates an {DiscreteLogarithmProof} from a given string @@ -96,7 +96,7 @@ def verify(generator, public_key) def verify_without_challenge(generator, public_key) left_hand_side = generator.mul(@response) # right_hand_side = commitment * 1 + public_key * challenge - right_hand_side = @commitment.mul([OpenSSL::BN.new(1), @challenge], [public_key]) + right_hand_side = @commitment.add(public_key.mul(@challenge)) left_hand_side == right_hand_side end @@ -104,7 +104,7 @@ def verify_without_challenge(generator, public_key) # Outputs the proof as a string with all values encoded as hex, concatenated by a comma # # @return (String) The encoded proof. - def to_s() + def to_s Crypto.point_to_hex(@commitment) + ',' + Crypto.bn_to_hex(@challenge) + ',' + Crypto.bn_to_hex(@response) end end diff --git a/lib/crypto/el_gamal_point_cryptogram.rb b/lib/crypto/el_gamal_point_cryptogram.rb index 42ee685..992f7d2 100644 --- a/lib/crypto/el_gamal_point_cryptogram.rb +++ b/lib/crypto/el_gamal_point_cryptogram.rb @@ -12,11 +12,11 @@ class ElGamalPointCryptogram def initialize(randomness, ciphertext) @randomness, @ciphertext = randomness, ciphertext - (randomness.is_a?(OpenSSL::PKey::EC::Point) && - randomness.on_curve?) or raise ArgumentError, 'randomness is not a valid point.' + ((randomness.is_a?(OpenSSL::PKey::EC::Point) && + randomness.on_curve?) || raise(ArgumentError, 'randomness is not a valid point.')) - (ciphertext.is_a?(OpenSSL::PKey::EC::Point) && - ciphertext.on_curve?) or raise ArgumentError, 'ciphertext is not a valid point.' + ((ciphertext.is_a?(OpenSSL::PKey::EC::Point) && + ciphertext.on_curve?) || raise(ArgumentError, 'ciphertext is not a valid point.')) end # Instantiates an {ElGamalPointCryptogram} from a given string @@ -46,7 +46,7 @@ def self.from_s(string) def self.encrypt(message, public_key, randomness) randomness_point = CURVE.group.generator.mul(randomness) # ciphertext = public_key * randomness + message * 1 - ciphertext_point = public_key.mul([randomness, OpenSSL::BN.new(1)], [message]) + ciphertext_point = public_key.mul(randomness).add(message) ElGamalPointCryptogram.new(randomness_point, ciphertext_point) end @@ -59,13 +59,13 @@ def decrypt(private_key) shared_secret = @randomness.mul(private_key) shared_secret.invert! - @ciphertext.mul([OpenSSL::BN.new(1), OpenSSL::BN.new(1)], [shared_secret]) + @ciphertext.add(shared_secret) end # Outputs the cryptogram as a string with both values encoded as hex, concatenated by a comma # # @return (String) The encoded cryptogram. - def to_s() + def to_s Crypto.point_to_hex(@randomness) + ',' + Crypto.point_to_hex(@ciphertext) end end diff --git a/lib/crypto/el_gamal_scalar_cryptogram.rb b/lib/crypto/el_gamal_scalar_cryptogram.rb index 330c970..4df642e 100644 --- a/lib/crypto/el_gamal_scalar_cryptogram.rb +++ b/lib/crypto/el_gamal_scalar_cryptogram.rb @@ -12,11 +12,11 @@ class ElGamalScalarCryptogram def initialize(randomness, ciphertext) @randomness, @ciphertext = randomness, ciphertext - (randomness.is_a?(OpenSSL::PKey::EC::Point) && - randomness.on_curve?) or raise ArgumentError, 'randomness is not a valid point.' + ((randomness.is_a?(OpenSSL::PKey::EC::Point) && + randomness.on_curve?) || raise(ArgumentError, 'randomness is not a valid point.')) - (ciphertext.is_a?(OpenSSL::BN) && - ciphertext == ciphertext % CURVE.group.order) or raise ArgumentError, 'ciphertext is not a valid integer.' + ((ciphertext.is_a?(OpenSSL::BN) && + ciphertext == ciphertext % CURVE.group.order) || raise(ArgumentError, 'ciphertext is not a valid integer.')) end # Instantiates an {ElGamalScalarCryptogram} from a given string @@ -44,7 +44,7 @@ def self.from_s(string) # @param randomness (OpenSSL::BN) The random value used to encrypt. # @return (ElGamalScalarCryptogram) a new scalar cryptogram def self.encrypt(message, public_key, randomness) - message == message % CURVE.group.order or raise ArgumentError, 'message is not a valid integer.' + (message == message % CURVE.group.order) || raise(ArgumentError, 'message is not a valid integer.') randomness_point = CURVE.group.generator.mul(randomness) @@ -79,7 +79,7 @@ def decrypt(private_key) # Outputs the cryptogram as a string with both values encoded as hex, concatenated by a comma # # @return (String) The encoded cryptogram. - def to_s() + def to_s Crypto.point_to_hex(@randomness) + ',' + Crypto.bn_to_hex(@ciphertext) end end diff --git a/lib/crypto/schnorr_signature.rb b/lib/crypto/schnorr_signature.rb index 47f4791..9852ce8 100644 --- a/lib/crypto/schnorr_signature.rb +++ b/lib/crypto/schnorr_signature.rb @@ -12,11 +12,11 @@ class SchnorrSignature def initialize(payload, signature) @payload, @signature = payload, signature - (payload.is_a?(OpenSSL::BN) && - payload == payload % CURVE.group.order) or raise ArgumentError, 'payload is not a valid integer.' + ((payload.is_a?(OpenSSL::BN) && + payload == payload % CURVE.group.order) || raise(ArgumentError, 'payload is not a valid integer.')) - (signature.is_a?(OpenSSL::BN) && - signature == signature % CURVE.group.order) or raise ArgumentError, 'signature is not a valid integer.' + ((signature.is_a?(OpenSSL::BN) && + signature == signature % CURVE.group.order) || raise(ArgumentError, 'signature is not a valid integer.')) end # Instantiates an {SchnorrSignature} from a given string @@ -43,7 +43,7 @@ def self.from_s(string) # @param private_key (OpenSSL::BN) The value of the signing key. # @return (SchnorrSignature) a new Schnorr signature def self.sign(message, private_key) - randomness_pair = CURVE.generate_key + randomness_pair = OpenSSL::PKey::EC.generate('secp256k1') hash_string = randomness_pair.public_key.to_octet_string(:compressed) + @@ -63,7 +63,7 @@ def self.sign(message, private_key) # @param public_key (OpenSSL::PKey::EC::Point) the value of the signature verification key. def verify(message, public_key) # randomness_point = generator * signature + public_key * payload - randomness_point = CURVE.group.generator.mul([@signature, @payload], [public_key]) + randomness_point = CURVE.group.generator.mul(@signature).add(public_key.mul(@payload)) hash_string = randomness_point.to_octet_string(:compressed) + @@ -76,7 +76,7 @@ def verify(message, public_key) # Outputs the signature as a string with both values encoded as hex, concatenated by a comma # # @return (String) The encoded signature. - def to_s() + def to_s Crypto.bn_to_hex(@payload) + ',' + Crypto.bn_to_hex(@signature) end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 4ad9fe0..99c8e73 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,4 +1,4 @@ -$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) +$LOAD_PATH.unshift File.expand_path('../lib', __dir__) require 'aion_cli' require 'minitest/autorun'