crypto_address_validator 0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a5acb742dfaf129cf78b31bd02f819a1f004e8e6b6243f3f4b440a4902175987
4
+ data.tar.gz: 566d0f3c14dbcbdb22474bdfe1964e53d073a241ad5c9d843934cd8104c6dab7
5
+ SHA512:
6
+ metadata.gz: 32e065985182686f76333525bba80e2277978a2577c074ad0904c5a36bdbf58a515993e521a4b89a124e70e9ed657d152ccb39f5421c0389910a02b2346d44f9
7
+ data.tar.gz: ca0739bc1a05275bbe620dd704116b9ec85cf273795e0f6796df37fdc1136bd6e4ab5be9f0caa5a1a02ab06532c206c2430441bf545bff28486d034928bbe84e
data/.coveralls.yml ADDED
@@ -0,0 +1,2 @@
1
+ repo_token: ATgUXu3s0jZaYdzAdUr8P8ytuMMbR9ueh
2
+
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ Gemfile.lock
11
+
12
+ # rspec failure tracking
13
+ .rspec_status
14
+
15
+ # Spec artifacts
16
+ /coverage
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,36 @@
1
+ require: rubocop-rspec
2
+
3
+ AllCops:
4
+ DisplayCopNames: true
5
+
6
+ Naming/AccessorMethodName:
7
+ Description: Check the naming of accessor methods for get_/set_.
8
+ Enabled: false
9
+
10
+ Style/DocumentationMethod:
11
+ RequireForNonPublicMethods: false
12
+
13
+ Documentation:
14
+ Enabled: false
15
+
16
+ Style/ClassAndModuleChildren:
17
+ Enabled: false
18
+
19
+ Style/NegatedIf:
20
+ Enabled: false
21
+
22
+ Metrics/LineLength:
23
+ Max: 120
24
+
25
+ Metrics/MethodLength:
26
+ Max: 30
27
+
28
+ Style/SymbolArray:
29
+ Enabled: true
30
+ EnforcedStyle: brackets
31
+
32
+ Style/StringLiterals:
33
+ EnforcedStyle: single_quotes
34
+ SupportedStyles:
35
+ - single_quotes
36
+
data/.travis.yml ADDED
@@ -0,0 +1,13 @@
1
+ ---
2
+ language: ruby
3
+ sudo: false
4
+ cache: bundler
5
+ rvm:
6
+ - 2.5.1
7
+ before_install: gem install bundler -v 1.16.3
8
+ script: bundle exec rspec spec
9
+ notifications:
10
+ email:
11
+ - justin@sequoir.com
12
+ after_success:
13
+ - CI=true TRAVIS=true coveralls --verbose
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ gem 'coveralls', '>= 0.8.17', require: false
8
+ gem 'pry', require: false
9
+
10
+ gem 'rubocop-rspec'
11
+ # Specify your gem's dependencies in crypto_address_validator.gemspec
12
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 vtm
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/Makefile ADDED
@@ -0,0 +1,6 @@
1
+
2
+ build:
3
+ gem build crypto_address_validator.gemspec
4
+
5
+ push:
6
+ gem push crypto_address_validator-0.1.5.gem
data/README.md ADDED
@@ -0,0 +1,150 @@
1
+
2
+ CryptoAddressValidator
3
+ ===============
4
+
5
+ [![Gem Version][gem-version-svg]][gem-version-link]
6
+ [![Build Status][build-status-svg]][build-status-link]
7
+ [![Code Climate][codeclimate-status-svg]][codeclimate-status-link]
8
+ [![Coverage Status][coverage-status-svg]][coverage-status-link]
9
+ [![Downloads][downloads-svg]][downloads-link]
10
+ [![Docs][docs-rubydoc-svg]][docs-rubydoc-link]
11
+ [![License][license-svg]][license-link]
12
+
13
+ Simple wallet address validator and normalizer for cryptocurrencies addresses in Ruby.
14
+
15
+ Inspired by [vtm9/adequate_crypto_address](https://github.com/vtm9/adequate_crypto_address).
16
+
17
+
18
+ ## Installation
19
+
20
+ Add this line to your application's Gemfile:
21
+
22
+ ```ruby
23
+ gem 'crypto_address_validator'
24
+ ```
25
+
26
+ Or install it yourself as:
27
+
28
+ ```bash
29
+ gem install crypto_address_validator
30
+ ```
31
+
32
+ ## Main API
33
+
34
+ ##### .valid? (address, currency [, type = :prod, type = [])
35
+
36
+ ###### Parameters
37
+ * address - Wallet address to validate.
38
+ * currency - Currency name string or symbol in any case, `:bitcoin` or `'BTC'` or `:btc` or `'BitCoin'`
39
+ * type - Optional. You can enforce validation with specific type or an array of types. Not all currencies support types.
40
+
41
+ > Returns true if the address (string) is a valid wallet address for the crypto currency specified, see below for supported currencies.
42
+
43
+ ### Supported crypto currencies
44
+
45
+ * Bitcoin/BTC, `'bitcoin'` or `'BTC'` types: `:segwit_v0_keyhash :segwit_v0_scripthash :hash160 :p2sh`
46
+ * BitcoinCash/BCH, `'bitcoincash'` or `'BCH'` types: `:p2sh :p2pkh :p2pkhtest :p2shtest`
47
+ * Dash, `'dash'` or `'DASH'` types: `:prod :test`
48
+ * Zcash/ZEC, `'zcash'` or `'ZEC'` types: `:prod :test`
49
+ * Ethereum/ETH, `'ethereum'` or `'ETH'`
50
+ * Ripple/XRP, `'ripple'` or `'XRP'`
51
+
52
+ ## Usage
53
+
54
+ ### Validation
55
+ ``` ruby
56
+ require 'crypto_address_validator'
57
+ # BTC
58
+ CryptoAddressValidator.valid?('12QeMLzSrB8XH8FvEzPMVoRxVAzTr5XM2y', 'BTC') #=> true
59
+ CryptoAddressValidator.valid?('3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt', :bitcoin, :p2sh) #=> true
60
+
61
+ # BTC(non SegWit address validation)
62
+ CryptoAddressValidator.valid?('3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt', :bitcoin, [:p2sh, :hash160]) #=> true
63
+
64
+ # BCH
65
+ CryptoAddressValidator.valid?('bitcoincash:qrtj3rd8524cndt2eew3s6wljqggmne00sgh4kfypk', :bch) #=> true
66
+ CryptoAddressValidator.valid?('mmRH4e9WW4ekZUP5HvBScfUyaSUjfQRyvD', :BCH, :p2pkhtest) #=> true
67
+
68
+ # ETH
69
+ CryptoAddressValidator.valid?('0xde709f2102306220921060314715629080e2fb77', :ETH) #=> true
70
+ CryptoAddressValidator.valid?('de709f2102306220921060314715629080e2fb77', :ethereum) #=> true
71
+ ```
72
+
73
+ ### Normalization
74
+ ###### *Not all currencies support this feature.
75
+ ``` ruby
76
+ require 'crypto_address_validator'
77
+
78
+ # BCH
79
+ CryptoAddressValidator.address('mmRH4e9WW4ekZUP5HvBScfUyaSUjfQRyvD', 'bch').cash_address #=> "bchtest:qpqtmmfpw79thzq5z7s0spcd87uhn6d34uqqem83hf"
80
+ CryptoAddressValidator.address('bitcoincash:qrtj3rd8524cndt2eew3s6wljqggmne00sgh4kfypk', 'bch').legacy_address #=> "1LcerwTc1oPsMtByDCNUXFxReZpN1EXHoe"
81
+
82
+ address_string = 'qrtj3rd8524cndt2eew3s6wljqggmne00sgh4kfypk'
83
+ addr = CryptoAddressValidator.address(address_string, 'bch')
84
+ addr.prefix #=> "bitcoincash"
85
+ addr.type #=> :p2pkh
86
+ addr.address #=> "bitcoincash:qrtj3rd8524cndt2eew3s6wljqggmne00sgh4kfypk"
87
+
88
+ # ETH
89
+ CryptoAddressValidator.address('D1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb', 'eth').address #=> "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb"
90
+ ```
91
+
92
+
93
+ ### ActiveRecord validation example
94
+ ``` ruby
95
+ class Model < ActiveRecord::Base
96
+ attribute :address, :string
97
+ attribute :dest_tag, :string
98
+ attribute :currency, :string
99
+
100
+ validate :validate_address_type
101
+ validate :validate_destination_tag
102
+
103
+ def validate_address_type
104
+ errors.add(:address, 'invalid address') unless CryptoAddressValidator.valid?(address, currency)
105
+ end
106
+
107
+ # for Ripple
108
+ def validate_destination_tag
109
+ errors.add(:dest_tag, 'invalid destination tag') if dest_tag.present? && !(dest_tag =~ /\A\d{1,10}\z/)
110
+ end
111
+ end
112
+
113
+ ```
114
+ ### Add your currnecy
115
+ ``` ruby
116
+ # frozen_string_literal: true
117
+ # for Rails /config/initializers/crypto_address_validator.rb
118
+ module CryptoAddressValidator
119
+ class Coin
120
+ attr_reader :address
121
+
122
+ def initialize(address_sring)
123
+ @address = address_sring
124
+ end
125
+
126
+ def valid?(_type)
127
+ address.present?
128
+ end
129
+ end
130
+ end
131
+
132
+ CryptoAddressValidator.valid?('addr', :coin) #=> true
133
+ ```
134
+
135
+ ## Development
136
+
137
+ Run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
138
+
139
+ ## Contributing
140
+
141
+ 1. Fork [the repo](https://github.com/sequoir/crypto_address_validator)
142
+ 2. Grab dependencies: `bundle install`
143
+ 3. Make sure everything is working: `bundle exec rake spec`
144
+ 4. Make your changes
145
+ 5. Test your changes
146
+ 5. Create a Pull Request
147
+
148
+ ## Notes
149
+
150
+ Bug reports and pull requests are welcome on GitHub at https://github.com/sequoir/crypto_address_validator/issues
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
5
+ require 'crypto_address_validator'
6
+
7
+ ACA = CryptoAddressValidator
8
+ include CryptoAddressValidator
9
+
10
+ require 'pry'
11
+ Pry.start
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'crypto_address_validator/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'crypto_address_validator'
9
+ spec.version = CryptoAddressValidator::VERSION
10
+ spec.authors = ['sequoir']
11
+ spec.email = ['engineering@sequoir.com']
12
+
13
+ spec.summary = 'A Ruby Library for dealing with validation cryptocurrency adresses.'
14
+ spec.description = 'A Ruby Library for dealing with validation cryptocurrency adresses.'
15
+ spec.homepage = 'https://github.com/sequoir/crypto_address_validator'
16
+
17
+ spec.license = 'MIT'
18
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'base58', '~> 0.2'
22
+ spec.add_dependency 'keccak', '1.2'
23
+ spec.add_development_dependency 'rake', '~> 13.0'
24
+ spec.add_development_dependency 'rspec', '~> 3.10'
25
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CryptoAddressValidator
4
+ # simple class for common altcoin addresses like legacy btc, ripple, dash, zec, etc.
5
+ class Altcoin
6
+ EXPECTED_LENGTH = 50
7
+ ADDRESS_TYPES = {}.freeze
8
+ ALPHABET_TYPE = :bitcoin
9
+
10
+ attr_reader :address, :type
11
+ alias raw_address address
12
+
13
+ def initialize(address)
14
+ @address = address
15
+ @type = address_type
16
+ end
17
+
18
+ def valid?(validated_type = nil)
19
+ if validated_type
20
+ type == validated_type.to_sym
21
+ else
22
+ !type.nil?
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :decoded
29
+
30
+ def address_type
31
+ @decoded = begin
32
+ decode_base58
33
+ rescue StandardError
34
+ nil
35
+ end
36
+ if decoded && decoded.bytesize == self.class::EXPECTED_LENGTH && valid_address_checksum?
37
+ self.class::ADDRESS_TYPES.each do |net_type, net_prefixes|
38
+ net_prefixes.each do |net_prefix|
39
+ return net_type if decoded.start_with?(net_prefix)
40
+ end
41
+ end
42
+ end
43
+ nil
44
+ end
45
+
46
+ def decode_base58
47
+ Base58.base58_to_binary(address, self.class::ALPHABET_TYPE).each_byte.map { |b| b.to_s(16).rjust(2, '0') }.join
48
+ end
49
+
50
+ def valid_address_checksum?
51
+ return false unless decoded
52
+
53
+ checksum(decoded[0...-8]) == decoded[-8..-1]
54
+ end
55
+
56
+ def checksum(bytes)
57
+ Digest::SHA256.hexdigest(Digest::SHA256.digest([bytes].pack('H*')))[0...8]
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base58'
4
+ require 'digest'
5
+
6
+ module CryptoAddressValidator
7
+ class InvalidAddress < StandardError; end
8
+ class Bch
9
+ class InvalidLegacyAddress < ::CryptoAddressValidator::InvalidAddress; end
10
+ class InvalidCashAddress < ::CryptoAddressValidator::InvalidAddress; end
11
+ include ::CryptoAddressValidator::Utils::Bch
12
+
13
+ TYPE_MAP = {
14
+ legacy: [
15
+ [:p2sh, 5],
16
+ [:p2pkh, 0],
17
+ [:p2shtest, 196],
18
+ [:p2pkhtest, 111]
19
+ ],
20
+ cash: [
21
+ [:p2sh, 8],
22
+ [:p2pkh, 0],
23
+ [:p2shtest, 8],
24
+ [:p2pkhtest, 0]
25
+ ]
26
+ }.freeze
27
+ DEFAULT_PREFIX = :bitcoincash
28
+
29
+ attr_reader :raw_address, :type, :payload, :prefix, :digest
30
+
31
+ def initialize(address)
32
+ @raw_address = address
33
+ normalize
34
+ end
35
+
36
+ def valid?(validated_type = nil)
37
+ if validated_type
38
+ type == validated_type.to_sym
39
+ else
40
+ !type.nil?
41
+ end
42
+ end
43
+
44
+ def address_type(address_code, address_type)
45
+ TYPE_MAP[address_code].each do |mapping|
46
+ return mapping if mapping.include?(address_type)
47
+ end
48
+
49
+ raise(CryptoAddressValidator::InvalidAddress, 'Could not determine address type')
50
+ end
51
+
52
+ def legacy_address
53
+ type_int = address_type(:legacy, type)[1]
54
+ input = code_list_to_string([type_int] + payload + Array(digest))
55
+ input += Digest::SHA256.digest(Digest::SHA256.digest(input))[0..3] unless digest
56
+ Base58.binary_to_base58(input, :bitcoin)
57
+ end
58
+
59
+ def cash_address
60
+ type_int = address_type(:cash, type)[1]
61
+ p = [type_int] + payload
62
+ p = convertbits(p, 8, 5)
63
+ checksum = calculate_cash_checksum(p)
64
+ "#{prefix}:#{b32encode(p + checksum)}"
65
+ end
66
+
67
+ alias address cash_address
68
+
69
+ private
70
+
71
+ def normalize
72
+ begin
73
+ from_cash_string
74
+ rescue InvalidCashAddress
75
+ from_legacy_string
76
+ end
77
+ rescue CryptoAddressValidator::InvalidAddress
78
+ nil
79
+ end
80
+
81
+ def from_cash_string
82
+ if (raw_address.upcase != raw_address) && (raw_address.downcase != raw_address)
83
+ raise(InvalidCashAddress, 'Cash address contains uppercase and lowercase characters')
84
+ end
85
+
86
+ @raw_address = raw_address.downcase
87
+ @raw_address = "#{DEFAULT_PREFIX}:#{raw_address}" if !raw_address.include?(':')
88
+
89
+ @prefix, base32string = raw_address.split(':')
90
+ decoded = b32decode(base32string)
91
+
92
+ raise(InvalidCashAddress, 'Bad cash address checksum') if !verify_cash_checksum(decoded)
93
+
94
+ converted = convertbits(decoded, 5, 8)
95
+ @type = address_type(:cash, converted[0].to_i)[0]
96
+ @payload = converted[1..-7]
97
+
98
+ @type = :p2shtest if prefix == 'bchtest' && type == :p2sh
99
+ @type = :p2pkhtest if prefix == 'bchtest' && type == :p2pkh
100
+ end
101
+
102
+ def from_legacy_string
103
+ decoded = nil
104
+ begin
105
+ decoded = Base58.base58_to_binary(raw_address, :bitcoin).bytes
106
+ rescue StandardError
107
+ raise(InvalidLegacyAddress, 'Could not decode legacy address')
108
+ end
109
+
110
+ @type = address_type(:legacy, decoded[0].to_i)[0]
111
+ @payload = decoded[1..-5]
112
+ @digest = decoded[-4..-1]
113
+ @prefix = DEFAULT_PREFIX
114
+
115
+ @type = :p2shtest if prefix == 'bchtest' && type == :p2sh
116
+ @type = :p2pkhtest if prefix == 'bchtest' && type == :p2pkh
117
+ @prefix = 'bchtest' if [:p2shtest, :p2pkhtest].include?(type)
118
+ end
119
+ end
120
+
121
+ Bitcoincash = Bch
122
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CryptoAddressValidator
4
+ class Btc
5
+ attr_reader :address, :type
6
+ alias raw_address address
7
+
8
+ def initialize(address)
9
+ @address = address
10
+ @type = address_type
11
+ end
12
+
13
+ def valid?(types = nil)
14
+ if types
15
+ types = [types].flatten.map(&:to_sym)
16
+ types.include?(address_type)
17
+ else
18
+ !address_type.nil?
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def address_type
25
+ segwit_decoded = begin
26
+ decode_segwit_address
27
+ rescue StandardError
28
+ nil
29
+ end
30
+ if segwit_decoded
31
+ witness_version, witness_program_hex = segwit_decoded
32
+ witness_program = [witness_program_hex].pack('H*')
33
+
34
+ return :segwit_v0_keyhash if witness_version == 0 && witness_program.bytesize == 20
35
+
36
+ return :segwit_v0_scripthash if witness_version == 0 && witness_program.bytesize == 32
37
+ end
38
+
39
+ base58_decoded = begin
40
+ decode_base58(address)
41
+ rescue StandardError
42
+ nil
43
+ end
44
+ if base58_decoded && base58_decoded.bytesize == 50 && valid_base58_address_checksum?(base58_decoded)
45
+ case base58_decoded[0...2]
46
+ when '00'
47
+ return :hash160
48
+ when '05'
49
+ return :p2sh
50
+ when '6f'
51
+ return :hash160test
52
+ when 'c4'
53
+ return :p2shtest
54
+ end
55
+ end
56
+
57
+ nil
58
+ end
59
+
60
+ def decode_segwit_address
61
+ actual_hrp, data = Utils::Bech32.decode(address)
62
+
63
+ return nil if actual_hrp.nil?
64
+
65
+ length = data.size
66
+ return nil if length == 0 || length > 65
67
+ return nil if actual_hrp != 'bc'
68
+ return nil if data[0] > 16
69
+
70
+ program = Utils::Bech32.convert_bits(data[1..-1], from_bits: 5, to_bits: 8, pad: false)
71
+ return nil if program.nil?
72
+
73
+ length = program.size
74
+ return nil if length < 2 || length > 40
75
+ return nil if data[0] == 0 && length != 20 && length != 32
76
+
77
+ program_hex = program.pack('C*').unpack('H*').first
78
+ [data[0], program_hex]
79
+ end
80
+
81
+ def decode_base58(_base58_val)
82
+ Base58.base58_to_binary(address, :bitcoin).each_byte.map { |b| b.to_s(16).rjust(2, '0') }.join
83
+ end
84
+
85
+ def valid_base58_address_checksum?(base58_decoded)
86
+ return false unless base58_decoded
87
+
88
+ checksum(base58_decoded[0...-8]) == base58_decoded[-8..-1]
89
+ end
90
+
91
+ def checksum(hex)
92
+ Digest::SHA256.hexdigest(Digest::SHA256.digest([hex].pack('H*')))[0...8]
93
+ end
94
+ end
95
+ Bitcoin = Btc
96
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CryptoAddressValidator
4
+ class Dash < Altcoin
5
+ ADDRESS_TYPES = { prod: %w[4c 10], test: %w[8c 13] }.freeze
6
+ end
7
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CryptoAddressValidator
4
+ class Eth
5
+ attr_reader :address, :raw_address
6
+
7
+ def initialize(address_sring)
8
+ @address = normalize(address_sring)
9
+ @raw_address = address_sring
10
+ end
11
+
12
+ def normalize(address_sring)
13
+ /\A0x/.match?(address_sring) ? address_sring : "0x#{address_sring}"
14
+ end
15
+
16
+ def valid?(_type = nil)
17
+ if !valid_format?
18
+ false
19
+ elsif not_checksummed?
20
+ true
21
+ else
22
+ checksum_matches?
23
+ end
24
+ end
25
+
26
+ def address_type; end
27
+
28
+ private
29
+
30
+ def checksummed
31
+ raise "Invalid address: #{address}" unless valid_format?
32
+
33
+ cased = unprefixed.chars.zip(checksum.chars).map do |char, check|
34
+ /[0-7]/.match?(check) ? char.downcase : char.upcase
35
+ end
36
+
37
+ normalize(cased.join)
38
+ end
39
+
40
+ private
41
+
42
+ def checksum_matches?
43
+ address == checksummed
44
+ end
45
+
46
+ def not_checksummed?
47
+ all_uppercase? || all_lowercase?
48
+ end
49
+
50
+ def all_uppercase?
51
+ address.match(/(?:0[xX])[A-F0-9]{40}/)
52
+ end
53
+
54
+ def all_lowercase?
55
+ address.match(/(?:0[xX])[a-f0-9]{40}/)
56
+ end
57
+
58
+ def valid_format?
59
+ address.match(/\A(?:0[xX])[a-fA-F0-9]{40}\z/)
60
+ end
61
+
62
+ def checksum
63
+ bin_to_hex(keccak256(unprefixed.downcase))
64
+ end
65
+
66
+ def unprefixed
67
+ remove_hex_prefix address
68
+ end
69
+
70
+ def remove_hex_prefix(s)
71
+ s[0, 2] == '0x' ? s[2..-1] : s
72
+ end
73
+
74
+ def bin_to_hex(string)
75
+ string.unpack1('H*')
76
+ end
77
+
78
+ def keccak256(x)
79
+ Digest::Keccak.new(256).digest(x)
80
+ end
81
+ end
82
+ Ethereum = Eth
83
+ end