crypto_address_validator 0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +2 -0
- data/.gitignore +16 -0
- data/.rspec +3 -0
- data/.rubocop.yml +36 -0
- data/.travis.yml +13 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +21 -0
- data/Makefile +6 -0
- data/README.md +150 -0
- data/Rakefile +8 -0
- data/bin/console +11 -0
- data/crypto_address_validator.gemspec +25 -0
- data/lib/crypto_address_validator/altcoin.rb +60 -0
- data/lib/crypto_address_validator/bch.rb +122 -0
- data/lib/crypto_address_validator/btc.rb +96 -0
- data/lib/crypto_address_validator/dash.rb +7 -0
- data/lib/crypto_address_validator/eth.rb +83 -0
- data/lib/crypto_address_validator/ltc.rb +8 -0
- data/lib/crypto_address_validator/utils/bch.rb +103 -0
- data/lib/crypto_address_validator/utils/bech32.rb +168 -0
- data/lib/crypto_address_validator/version.rb +5 -0
- data/lib/crypto_address_validator/xrp.rb +9 -0
- data/lib/crypto_address_validator/zec.rb +9 -0
- data/lib/crypto_address_validator.rb +32 -0
- data/spec/crypto_address_validator/bch_spec.rb +77 -0
- data/spec/crypto_address_validator_spec.rb +196 -0
- data/spec/spec_helper.rb +17 -0
- metadata +126 -0
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
data/.gitignore
ADDED
data/.rspec
ADDED
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
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
data/bin/console
ADDED
@@ -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,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
|