crypto_address_validator 0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptoAddressValidator
|
4
|
+
module Utils
|
5
|
+
module Bch
|
6
|
+
CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'
|
7
|
+
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def code_list_to_string(code_list)
|
11
|
+
code_list.map { |i| Array(i).pack('C*') }.flatten.join
|
12
|
+
end
|
13
|
+
|
14
|
+
def b32decode(inputs)
|
15
|
+
out = []
|
16
|
+
return out unless inputs
|
17
|
+
|
18
|
+
inputs.split('').each do |letter|
|
19
|
+
out.push(CHARSET.index(letter))
|
20
|
+
end
|
21
|
+
out
|
22
|
+
end
|
23
|
+
|
24
|
+
def polymod(values)
|
25
|
+
chk = 1
|
26
|
+
generator = [
|
27
|
+
[0x01, 0x98f2bc8e61],
|
28
|
+
[0x02, 0x79b76d99e2],
|
29
|
+
[0x04, 0xf33e5fb3c4],
|
30
|
+
[0x08, 0xae2eabe2a8],
|
31
|
+
[0x10, 0x1e4f43e470]
|
32
|
+
]
|
33
|
+
values.each do |value|
|
34
|
+
top = chk >> 35
|
35
|
+
chk = ((chk & 0x07ffffffff) << 5) ^ value
|
36
|
+
generator.each do |i|
|
37
|
+
chk ^= i[1] if (top & i[0]) != 0
|
38
|
+
end
|
39
|
+
end
|
40
|
+
chk ^ 1
|
41
|
+
end
|
42
|
+
|
43
|
+
def expanded_prefix
|
44
|
+
val = if prefix
|
45
|
+
prefix.to_s.split('').map do |i|
|
46
|
+
i.ord & 0x1f
|
47
|
+
end
|
48
|
+
else
|
49
|
+
[]
|
50
|
+
end
|
51
|
+
|
52
|
+
val + [0]
|
53
|
+
end
|
54
|
+
|
55
|
+
def calculate_cash_checksum(payload)
|
56
|
+
poly = polymod(expanded_prefix + payload + [0, 0, 0, 0, 0, 0, 0, 0])
|
57
|
+
out = []
|
58
|
+
8.times do |i|
|
59
|
+
out.push((poly >> 5 * (7 - i)) & 0x1f)
|
60
|
+
end
|
61
|
+
out
|
62
|
+
end
|
63
|
+
|
64
|
+
def verify_cash_checksum(payload)
|
65
|
+
polymod(expanded_prefix + payload) == 0
|
66
|
+
rescue TypeError
|
67
|
+
raise CryptoAddressValidator::InvalidAddress
|
68
|
+
end
|
69
|
+
|
70
|
+
def b32encode(inputs)
|
71
|
+
out = ''
|
72
|
+
inputs.each do |char_code|
|
73
|
+
out += CHARSET[char_code].to_s
|
74
|
+
end
|
75
|
+
out
|
76
|
+
end
|
77
|
+
|
78
|
+
def convertbits(data, frombits, tobits, pad = true)
|
79
|
+
acc = 0
|
80
|
+
bits = 0
|
81
|
+
ret = []
|
82
|
+
maxv = (1 << tobits) - 1
|
83
|
+
max_acc = (1 << (frombits + tobits - 1)) - 1
|
84
|
+
data.each do |value|
|
85
|
+
return nil if value < 0 || ((value >> frombits) != 0)
|
86
|
+
|
87
|
+
acc = ((acc << frombits) | value) & max_acc
|
88
|
+
bits += frombits
|
89
|
+
while bits >= tobits
|
90
|
+
bits -= tobits
|
91
|
+
ret.push((acc >> bits) & maxv)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
if pad
|
95
|
+
ret.push((acc << (tobits - bits)) & maxv) if bits != 0
|
96
|
+
elsif bits >= frombits || (((acc << (tobits - bits)) & maxv) != 0)
|
97
|
+
return nil
|
98
|
+
end
|
99
|
+
ret
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptoAddressValidator
|
4
|
+
# Ruby reference implementation: https://github.com/sipa/bech32/tree/master/ref/c
|
5
|
+
module Utils
|
6
|
+
module Bech32
|
7
|
+
CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'.unpack('C*')
|
8
|
+
CHARSET_REV = [
|
9
|
+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
10
|
+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
11
|
+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
12
|
+
15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1,
|
13
|
+
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
|
14
|
+
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1,
|
15
|
+
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
|
16
|
+
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def polymod_step(pre)
|
21
|
+
b = pre >> 25
|
22
|
+
((pre & 0x1FFFFFF) << 5) ^ \
|
23
|
+
(-((b >> 0) & 1) & 0x3b6a57b2) ^ \
|
24
|
+
(-((b >> 1) & 1) & 0x26508e6d) ^ \
|
25
|
+
(-((b >> 2) & 1) & 0x1ea119fa) ^ \
|
26
|
+
(-((b >> 3) & 1) & 0x3d4233dd) ^ \
|
27
|
+
(-((b >> 4) & 1) & 0x2a1462b3)
|
28
|
+
end
|
29
|
+
|
30
|
+
# def encode(hrp, data)
|
31
|
+
# buf = []
|
32
|
+
# chk = 1
|
33
|
+
|
34
|
+
# hrp.unpack('C*').each do |ch|
|
35
|
+
# return nil if ch < 33 || ch > 126
|
36
|
+
# return nil if ch >= 'A'.ord && ch <= 'Z'.ord
|
37
|
+
|
38
|
+
# chk = polymod_step(chk) ^ (ch >> 5)
|
39
|
+
# end
|
40
|
+
|
41
|
+
# return nil if (hrp.bytesize + 7 + data.size) > 90
|
42
|
+
|
43
|
+
# chk = polymod_step(chk)
|
44
|
+
# hrp.unpack('C*').each do |ch|
|
45
|
+
# chk = polymod_step(chk) ^ (ch & 0x1f)
|
46
|
+
# buf << ch
|
47
|
+
# end
|
48
|
+
|
49
|
+
# buf << '1'.ord
|
50
|
+
|
51
|
+
# data.each do |i|
|
52
|
+
# return nil if (i >> 5) != 0
|
53
|
+
|
54
|
+
# chk = polymod_step(chk) ^ i
|
55
|
+
# buf << CHARSET[i]
|
56
|
+
# end
|
57
|
+
|
58
|
+
# 6.times do
|
59
|
+
# chk = polymod_step(chk)
|
60
|
+
# end
|
61
|
+
|
62
|
+
# chk ^= 1
|
63
|
+
|
64
|
+
# 6.times do |i|
|
65
|
+
# buf << CHARSET[(chk >> ((5 - i) * 5)) & 0x1f]
|
66
|
+
# end
|
67
|
+
|
68
|
+
# buf.pack('C*')
|
69
|
+
# end
|
70
|
+
|
71
|
+
# rubocop:disable CyclomaticComplexity,PerceivedComplexity
|
72
|
+
def decode(input)
|
73
|
+
chk = 1
|
74
|
+
input_len = input.bytesize
|
75
|
+
have_lower = false
|
76
|
+
have_upper = false
|
77
|
+
|
78
|
+
return nil if input_len < 8 || input_len > 90
|
79
|
+
|
80
|
+
data_len = 0
|
81
|
+
data_len += 1 while data_len < input_len && input[(input_len - 1) - data_len] != '1'
|
82
|
+
|
83
|
+
hrp_len = input_len - (1 + data_len)
|
84
|
+
return nil if hrp_len < 1 || data_len < 6
|
85
|
+
|
86
|
+
hrp = []
|
87
|
+
hrp_len.times do |i|
|
88
|
+
ch = input[i].ord
|
89
|
+
return nil if ch < 33 || ch > 126
|
90
|
+
|
91
|
+
if ch >= 'a'.ord && ch <= 'z'.ord
|
92
|
+
have_lower = true
|
93
|
+
elsif ch >= 'A'.ord && ch <= 'Z'.ord
|
94
|
+
have_upper = true
|
95
|
+
ch = (ch - 'A'.ord) + 'a'.ord
|
96
|
+
end
|
97
|
+
|
98
|
+
hrp << ch
|
99
|
+
chk = polymod_step(chk) ^ (ch >> 5)
|
100
|
+
end
|
101
|
+
|
102
|
+
chk = polymod_step(chk)
|
103
|
+
|
104
|
+
hrp_len.times do |i|
|
105
|
+
chk = polymod_step(chk) ^ (input[i].ord & 0x1f)
|
106
|
+
end
|
107
|
+
|
108
|
+
data = []
|
109
|
+
i = hrp_len + 1
|
110
|
+
while i < input_len
|
111
|
+
ch = input[i].ord
|
112
|
+
v = (ch & 0x80) != 0 ? -1 : CHARSET_REV[ch]
|
113
|
+
|
114
|
+
have_lower = true if ch >= 'a'.ord && ch <= 'z'.ord
|
115
|
+
have_upper = true if ch >= 'A'.ord && ch <= 'Z'.ord
|
116
|
+
return nil if v == -1
|
117
|
+
|
118
|
+
chk = polymod_step(chk) ^ v
|
119
|
+
data << v if (i + 6) < input_len
|
120
|
+
i += 1
|
121
|
+
end
|
122
|
+
|
123
|
+
return nil if have_lower && have_upper
|
124
|
+
return nil if chk != 1
|
125
|
+
|
126
|
+
[hrp.pack('C*'), data]
|
127
|
+
end
|
128
|
+
# rubocop:enable CyclomaticComplexity,PerceivedComplexity
|
129
|
+
|
130
|
+
# Utility for converting bytes of data between bases. These is used for
|
131
|
+
# BIP 173 address encoding/decoding to convert between sequences of bytes
|
132
|
+
# representing 8-bit values and groups of 5 bits. Conversions may be padded
|
133
|
+
# with trailing 0 bits to the nearest byte boundary. Returns nil if
|
134
|
+
# conversion requires padding and pad is false.
|
135
|
+
#
|
136
|
+
# For example:
|
137
|
+
#
|
138
|
+
# convert_bits("\xFF\xFF", from_bits: 8, to_bits: 5, pad: true)
|
139
|
+
# => "\x1F\x1F\x1F\10"
|
140
|
+
#
|
141
|
+
# See https://github.com/bitcoin/bitcoin/blob/595a7bab23bc21049526229054ea1fff1a29c0bf/src/utilstrencodings.h#L154
|
142
|
+
def convert_bits(chunks, from_bits:, to_bits:, pad:)
|
143
|
+
output_mask = (1 << to_bits) - 1
|
144
|
+
buffer_mask = (1 << (from_bits + to_bits - 1)) - 1
|
145
|
+
|
146
|
+
buffer = 0
|
147
|
+
bits = 0
|
148
|
+
|
149
|
+
output = []
|
150
|
+
chunks.each do |chunk|
|
151
|
+
buffer = ((buffer << from_bits) | chunk) & buffer_mask
|
152
|
+
bits += from_bits
|
153
|
+
while bits >= to_bits
|
154
|
+
bits -= to_bits
|
155
|
+
output << ((buffer >> bits) & output_mask)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
output << ((buffer << (to_bits - bits)) & output_mask) if pad && bits > 0
|
160
|
+
|
161
|
+
return nil if !pad && (bits >= from_bits || ((buffer << (to_bits - bits)) & output_mask) != 0)
|
162
|
+
|
163
|
+
output
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'crypto_address_validator/utils/bech32'
|
4
|
+
require 'crypto_address_validator/utils/bch'
|
5
|
+
|
6
|
+
require 'crypto_address_validator/altcoin'
|
7
|
+
require 'crypto_address_validator/bch'
|
8
|
+
require 'crypto_address_validator/eth'
|
9
|
+
require 'crypto_address_validator/btc'
|
10
|
+
require 'crypto_address_validator/xrp'
|
11
|
+
require 'crypto_address_validator/dash'
|
12
|
+
require 'crypto_address_validator/zec'
|
13
|
+
require 'crypto_address_validator/ltc'
|
14
|
+
|
15
|
+
module CryptoAddressValidator
|
16
|
+
class UnknownCurrency < StandardError; end
|
17
|
+
module_function
|
18
|
+
|
19
|
+
def valid?(address, currency, type = nil)
|
20
|
+
address(address, currency).valid?(type)
|
21
|
+
end
|
22
|
+
|
23
|
+
def address(address, currency)
|
24
|
+
CryptoAddressValidator.const_get(currency.capitalize).new(address)
|
25
|
+
rescue NameError
|
26
|
+
raise UnknownCurrency, "Wrong currency #{currency}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def address_type(address, currency)
|
30
|
+
CryptoAddressValidator.const_get(currency.capitalize).new(address).address_type
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe(CryptoAddressValidator::Bch) do
|
4
|
+
let(:legacy_p2sh) { '3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC' }
|
5
|
+
let(:legacy_p2pkh) { '155fzsEBHy9Ri2bMQ8uuuR3tv1YzcDywd4' }
|
6
|
+
let(:cashaddr_p2sh) { 'bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq' }
|
7
|
+
let(:cashaddr_p2pkh) { 'bitcoincash:qqkv9wr69ry2p9l53lxp635va4h86wv435995w8p2h' }
|
8
|
+
let(:cashaddr_p2pkh_testnet) { 'bchtest:qpqtmmfpw79thzq5z7s0spcd87uhn6d34uqqem83hf' }
|
9
|
+
let(:legacy_p2pkh_testnet) { 'mmRH4e9WW4ekZUP5HvBScfUyaSUjfQRyvD' }
|
10
|
+
let(:cashaddr_p2sh_testnet) { 'bchtest:pp8f7ww2g6y07ypp9r4yendrgyznysc9kqxh6acwu3' }
|
11
|
+
let(:legacy_p2sh_testnet) { '2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc' }
|
12
|
+
let(:mixed_case_cashadddr) { 'bitcoincash:qqkv9wr69ry2p9l53lxP635va4h86wv435995w8p2H' }
|
13
|
+
|
14
|
+
describe '#legacy_address' do
|
15
|
+
it 'converts legacy testnet p2pkh' do
|
16
|
+
expect(described_class.new(legacy_p2pkh_testnet).legacy_address).to eq(legacy_p2pkh_testnet)
|
17
|
+
expect(described_class.new(cashaddr_p2pkh_testnet).legacy_address).to eq(legacy_p2pkh_testnet)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'converts legacy testnet p2sh' do
|
21
|
+
expect(described_class.new(legacy_p2sh_testnet).legacy_address).to eq(legacy_p2sh_testnet)
|
22
|
+
expect(described_class.new(cashaddr_p2sh_testnet).legacy_address).to eq(legacy_p2sh_testnet)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'converts to legacy p2sh' do
|
26
|
+
expect(described_class.new(legacy_p2sh).legacy_address).to eq(legacy_p2sh)
|
27
|
+
expect(described_class.new(cashaddr_p2sh).legacy_address).to eq(legacy_p2sh)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'converts to legacy p2pkh' do
|
31
|
+
expect(described_class.new(legacy_p2pkh).legacy_address).to eq(legacy_p2pkh)
|
32
|
+
expect(described_class.new(cashaddr_p2pkh).legacy_address).to eq(legacy_p2pkh)
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'uppercase cashaddr address' do
|
36
|
+
it 'converts to legacy p2sh' do
|
37
|
+
expect(described_class.new(cashaddr_p2sh.upcase).legacy_address).to eq(legacy_p2sh)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'converts to legacy p2pkh' do
|
41
|
+
expect(described_class.new(cashaddr_p2pkh.upcase).legacy_address).to eq(legacy_p2pkh)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context '#cash_address' do
|
47
|
+
it 'converts cash testnet p2pkh' do
|
48
|
+
expect(described_class.new(legacy_p2pkh_testnet).cash_address).to eq(cashaddr_p2pkh_testnet)
|
49
|
+
expect(described_class.new(cashaddr_p2pkh_testnet).cash_address).to eq(cashaddr_p2pkh_testnet)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'converts cash testnet p2sh' do
|
53
|
+
expect(described_class.new(legacy_p2sh_testnet).cash_address).to eq(cashaddr_p2sh_testnet)
|
54
|
+
expect(described_class.new(cashaddr_p2sh_testnet).cash_address).to eq(cashaddr_p2sh_testnet)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'converts to cash p2sh' do
|
58
|
+
expect(described_class.new(legacy_p2sh).cash_address).to eq(cashaddr_p2sh)
|
59
|
+
expect(described_class.new(cashaddr_p2sh).cash_address).to eq(cashaddr_p2sh)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'converts to cash p2pkh' do
|
63
|
+
expect(described_class.new(legacy_p2pkh).cash_address).to eq(cashaddr_p2pkh)
|
64
|
+
expect(described_class.new(cashaddr_p2pkh).cash_address).to eq(cashaddr_p2pkh)
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'uppercase cashaddr address' do
|
68
|
+
it 'converts to cash p2sh' do
|
69
|
+
expect(described_class.new(cashaddr_p2sh.upcase).cash_address).to eq(cashaddr_p2sh)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'converts to cash p2pkh' do
|
73
|
+
expect(described_class.new(cashaddr_p2pkh.upcase).cash_address).to eq(cashaddr_p2pkh)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe(CryptoAddressValidator) do
|
4
|
+
describe '.valid?' do
|
5
|
+
context 'Bitcoin' do
|
6
|
+
it 'validates hash160 addresses' do
|
7
|
+
expect(described_class).to be_valid('12KYrjTdVGjFMtaxERSk3gphreJ5US8aUP', 'bitcoin')
|
8
|
+
expect(described_class).to be_valid('12QeMLzSrB8XH8FvEzPMVoRxVAzTr5XM2y', 'BTC')
|
9
|
+
expect(described_class).to be_valid('12QeMLzSrB8XH8FvEzPMVoRxVAzTr5XM2y', 'Bitcoin')
|
10
|
+
expect(described_class).to be_valid('12QeMLzSrB8XH8FvEzPMVoRxVAzTr5XM2y', 'btc')
|
11
|
+
expect(described_class).to be_valid('12QeMLzSrB8XH8FvEzPMVoRxVAzTr5XM2y', 'btc', :hash160)
|
12
|
+
|
13
|
+
# testnet
|
14
|
+
expect(described_class).to be_valid('mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhef', 'bitcoin', 'hash160test')
|
15
|
+
expect(described_class).to be_valid('mv4rnyY3Su5gjcDNzbMLKBQkBicCtHUtFB', :btc)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'validates p2sh addresses' do
|
19
|
+
expect(described_class).to be_valid('3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt', 'BTC')
|
20
|
+
expect(described_class).to be_valid('3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt', 'bitcoin', 'p2sh')
|
21
|
+
|
22
|
+
# testnet
|
23
|
+
expect(described_class).to be_valid('2MxKEf2su6FGAUfCEAHreGFQvEYrfYNHvL7', 'btc')
|
24
|
+
expect(described_class).to be_valid('2MxKEf2su6FGAUfCEAHreGFQvEYrfYNHvL7', 'bitcoin', 'p2shtest')
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'validates segwit addresses' do
|
28
|
+
expect(described_class).to be_valid('BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4', 'BTC')
|
29
|
+
expect(described_class).to be_valid('bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq', 'bitcoin')
|
30
|
+
expect(described_class).to be_valid('bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq', 'bitcoin', :segwit_v0_keyhash)
|
31
|
+
expect(described_class).to be_valid('bc1qc7slrfxkknqcq2jevvvkdgvrt8080852dfjewde450xdlk4ugp7szw5tk9', 'BTC')
|
32
|
+
expect(described_class).to be_valid('bc1qc7slrfxkknqcq2jevvvkdgvrt8080852dfjewde450xdlk4ugp7szw5tk9', 'bitcoin', 'segwit_v0_scripthash')
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'validates multiple types at once' do
|
36
|
+
expect(described_class).to be_valid('12QeMLzSrB8XH8FvEzPMVoRxVAzTr5XM2y', 'btc', [:p2sh, :hash160])
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'validates wrong addresses' do
|
40
|
+
expect(described_class).not_to be_valid('asdf', :bitcoin)
|
41
|
+
expect(described_class).not_to be_valid('3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt', 'bitcoin', :segwit_v0_keyhash)
|
42
|
+
expect(described_class).not_to be_valid('3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt', 'bitcoin', 'asdf')
|
43
|
+
expect(described_class).not_to be_valid('tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty', :bitcoin)
|
44
|
+
expect(described_class).not_to be_valid('bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5', 'bitcoin')
|
45
|
+
expect(described_class).not_to be_valid('BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2', 'bitcoin')
|
46
|
+
expect(described_class).not_to be_valid('bc1rw5uspcuh', 'bitcoin')
|
47
|
+
expect(described_class).not_to be_valid('bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90', 'bitcoin')
|
48
|
+
expect(described_class).not_to be_valid('BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P', 'bitcoin')
|
49
|
+
expect(described_class).not_to be_valid('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7', 'BTC')
|
50
|
+
expect(described_class).not_to be_valid('bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du', 'bitcoin')
|
51
|
+
expect(described_class).not_to be_valid('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv', 'Bitcoin')
|
52
|
+
expect(described_class).not_to be_valid('bc1gmk9yu', 'bitcoin')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'Bitcoincash' do
|
57
|
+
it 'validates legacy addresses' do
|
58
|
+
expect(described_class).to be_valid('3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC', :bch, :p2sh)
|
59
|
+
expect(described_class).to be_valid('155fzsEBHy9Ri2bMQ8uuuR3tv1YzcDywd4', 'bitcoincash', :p2pkh)
|
60
|
+
expect(described_class).to be_valid('2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc', 'BCH', :p2shtest)
|
61
|
+
expect(described_class).to be_valid('mmRH4e9WW4ekZUP5HvBScfUyaSUjfQRyvD', :BCH, :p2pkhtest)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'validates cash addresses' do
|
65
|
+
expect(described_class).to be_valid('bitcoincash:qqkv9wr69ry2p9l53lxp635va4h86wv435995w8p2h', :bch, :p2pkh)
|
66
|
+
expect(described_class).to be_valid('bitcoincash:pqdg9uq52wzhf228hweext9j2jdjgdpj9qt7xxfngd', :bitcoincash, :p2sh)
|
67
|
+
expect(described_class).to be_valid('bchtest:qpqtmmfpw79thzq5z7s0spcd87uhn6d34uqqem83hf', :Bitcoincash, :p2pkhtest)
|
68
|
+
expect(described_class).to be_valid('bchtest:pp8f7ww2g6y07ypp9r4yendrgyznysc9kqxh6acwu3', :BCH, :p2shtest)
|
69
|
+
expect(described_class).to be_valid('bitcoincash:qrtj3rd8524cndt2eew3s6wljqggmne00sgh4kfypk', :bitcoincash)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'validates cash addresses without prefix addresses' do
|
73
|
+
expect(described_class).to be_valid('qrtj3rd8524cndt2eew3s6wljqggmne00sgh4kfypk', :bitcoincash)
|
74
|
+
expect(described_class).to be_valid('pqdg9uq52wzhf228hweext9j2jdjgdpj9qt7xxfngd', :bitcoincash, :p2sh)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'validates wrong addresses' do
|
78
|
+
expect(described_class).not_to be_valid('bitcoincash:qqkv9wr69ry2p9l53lxP635va4h86wv435995w8p2H', :bch)
|
79
|
+
expect(described_class).not_to be_valid('wrong', :bch)
|
80
|
+
expect(described_class).not_to be_valid('bitcoincash:wrong', :bch)
|
81
|
+
expect(described_class).not_to be_valid('bitcoincash:123', :bch)
|
82
|
+
|
83
|
+
expect(described_class).not_to be_valid('bitcoincash:qqkv9wr69ry2p9l53lxp635va4h86wv435995w8p2h', :bch, :p2pkhtest)
|
84
|
+
expect(described_class).not_to be_valid('bitcoincash:pqdg9uq52wzhf228hweext9j2jdjgdpj9qt7xxfngd', :bitcoincash, :p2pkhtest)
|
85
|
+
expect(described_class).not_to be_valid('bchtest:qpqtmmfpw79thzq5z7s0spcd87uhn6d34uqqem83hf', :Bitcoincash, :p2sh)
|
86
|
+
expect(described_class).not_to be_valid('bchtest:pp8f7ww2g6y07ypp9r4yendrgyznysc9kqxh6acwu3', :BCH, :p2sh)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'Ethereum' do
|
91
|
+
it 'validates addresses' do
|
92
|
+
expect(described_class).to be_valid('0xE37c0D48d68da5c5b14E5c1a9f1CFE802776D9FF', 'ethereum')
|
93
|
+
expect(described_class).to be_valid('0xa00354276d2fC74ee91e37D085d35748613f4748', :ethereum)
|
94
|
+
expect(described_class).to be_valid('0xAff4d6793F584a473348EbA058deb8caad77a288', :ETH)
|
95
|
+
expect(described_class).to be_valid('0xc6d9d2cd449a754c494264e1809c50e34d64562b', 'ETH')
|
96
|
+
expect(described_class).to be_valid('0x52908400098527886E0F7030069857D2E4169EE7', 'ETH')
|
97
|
+
expect(described_class).to be_valid('0x8617E340B3D01FA5F11F306F4090FD50E238070D', 'ETH')
|
98
|
+
expect(described_class).to be_valid('0xde709f2102306220921060314715629080e2fb77', 'ETH')
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'validates without prefixes addresses' do
|
102
|
+
expect(described_class).to be_valid('27b1fdb04752bbc536007a920d24acb045561c26', 'ETH')
|
103
|
+
expect(described_class).to be_valid('5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed', 'ETH')
|
104
|
+
expect(described_class).to be_valid('fB6916095ca1df60bB79Ce92cE3Ea74c37c5d359', 'ETH')
|
105
|
+
expect(described_class).to be_valid('dbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB', 'ETH')
|
106
|
+
expect(described_class).to be_valid('D1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb', 'ETH')
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'validates wrong addresses' do
|
110
|
+
expect(described_class).not_to be_valid('wrong', :ETH)
|
111
|
+
expect(described_class).not_to be_valid('0xD1110A0cf47c7B9Be7A2E6BA89F429762e7b9aDb', 'ETH')
|
112
|
+
expect(described_class).not_to be_valid('a10354276d2fC74ee91e37D085d35748613f4748', :ethereum)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'Ripple' do
|
117
|
+
it 'validates addresses' do
|
118
|
+
expect(described_class).to be_valid('rPMLwSwyyULN2acf5JKB1nj8F8Eu8pVMV8', :ripple)
|
119
|
+
expect(described_class).to be_valid('rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn', :ripple)
|
120
|
+
expect(described_class).to be_valid('rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn', 'RIPPLE')
|
121
|
+
expect(described_class).to be_valid('r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV', 'XRP')
|
122
|
+
expect(described_class).to be_valid('rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', :XRP)
|
123
|
+
expect(described_class).to be_valid('rDTXLQ7ZKZVKz33zJbHjgVShjsBnqMBhmN', 'XRP')
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'validates wrong addresses' do
|
127
|
+
expect(described_class).not_to be_valid('wrong', :xrp)
|
128
|
+
expect(described_class).not_to be_valid('r3kmLJN5D28dHuH8vZNUZpMC43pEHpaoc1', :xrp)
|
129
|
+
expect(described_class).not_to be_valid('r1kmLJN5D28dHuH8vZNUZpMC43pEHpaocV', 'ripple')
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context 'Dash' do
|
134
|
+
it 'validates addresses' do
|
135
|
+
expect(described_class).to be_valid('Xx4dYKgz3Zcv6kheaqog3fynaKWjbahb6b', :dash)
|
136
|
+
expect(described_class).to be_valid('XcY4WJ6Z2Q8w7vcYER1JypC8s2oa3SQ1b1', 'DASH')
|
137
|
+
expect(described_class).to be_valid('XqMkVUZnqe3w4xvgdZRtZoe7gMitDudGs4', 'DASH', :prod)
|
138
|
+
expect(described_class).to be_valid('yPv7h2i8v3dJjfSH4L3x91JSJszjdbsJJA', :DASH, 'test')
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'validates wrong addresses' do
|
142
|
+
expect(described_class).not_to be_valid('wrong', :dash)
|
143
|
+
expect(described_class).not_to be_valid('yPv7h2i8v3dJ1fSH4L3x91JSJszjdbsJJA', :dash)
|
144
|
+
expect(described_class).not_to be_valid('XqMkVUZnqe3w4xvgdZRtZoe7gMitDudGs4', 'dash', :test)
|
145
|
+
expect(described_class).not_to be_valid('yPv7h2i8v3dJjfSH4L3x91JSJszjdbsJJA', :DASH, :prod)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context 'Zcash' do
|
150
|
+
it 'validates addresses' do
|
151
|
+
expect(described_class).to be_valid('t1U9yhDa5XEjgfnTgZoKddeSiEN1aoLkQxq', :zec)
|
152
|
+
expect(described_class).to be_valid('t3Vz22vK5z2LcKEdg16Yv4FFneEL1zg9ojd', 'zcash', :prod)
|
153
|
+
expect(described_class).to be_valid('t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi', 'ZEC', :test)
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'validates wrong addresses' do
|
157
|
+
expect(described_class).not_to be_valid('wrong', :zec)
|
158
|
+
expect(described_class).not_to be_valid('t1Y9yhDa5XEjgfnTgZoKddeSiEN1aoLkQxq', :zcash)
|
159
|
+
expect(described_class).not_to be_valid('t3Yz22vK5z2LcKEdg16Yv4FFneEL1zg9ojd', :ZEC)
|
160
|
+
expect(described_class).not_to be_valid('t2YNzUUx8mWBCRYPRezvA363EYXyEpHokyi', :zcash, :test)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context 'Litecoin' do
|
165
|
+
it 'validates addresses' do
|
166
|
+
expect(described_class).to be_valid('LVg2kJoFNg45Nbpy53h7Fe1wKyeXVRhMH9', :ltc)
|
167
|
+
expect(described_class).to be_valid('LVg2kJoFNg45Nbpy53h7Fe1wKyeXVRhMH9', 'ltc', :prod)
|
168
|
+
expect(described_class).to be_valid('LTpYZG19YmfvY2bBDYtCKpunVRw7nVgRHW', 'LTC')
|
169
|
+
expect(described_class).to be_valid('Lb6wDP2kHGyWC7vrZuZAgV7V4ECyDdH7a6', 'Litecoin')
|
170
|
+
expect(described_class).to be_valid('mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhef', 'Litecoin', :test)
|
171
|
+
|
172
|
+
expect(described_class).to be_valid('3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt', 'LTC')
|
173
|
+
expect(described_class).to be_valid('2MxKEf2su6FGAUfCEAHreGFQvEYrfYNHvL7', 'LTC', :test)
|
174
|
+
expect(described_class).to be_valid('QW2SvwjaJU8LD6GSmtm1PHnBG2xPuxwZFy', 'LTC', :test)
|
175
|
+
expect(described_class).to be_valid('QjpzxpbLp5pCGsCczMbfh1uhC3P89QZavY', 'LTC', :test)
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'validates wrong addresses' do
|
179
|
+
expect(described_class).not_to be_valid('wrong', :zec)
|
180
|
+
expect(described_class).not_to be_valid('t1Y9yhDa5XEjgfnTgZoKddeSiEN1aoLkQxq', :zcash)
|
181
|
+
expect(described_class).not_to be_valid('t3Yz22vK5z2LcKEdg16Yv4FFneEL1zg9ojd', :ZEC)
|
182
|
+
expect(described_class).not_to be_valid('t2YNzUUx8mWBCRYPRezvA363EYXyEpHokyi', :zcash, :test)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe '.address' do
|
188
|
+
it 'returns insance' do
|
189
|
+
expect(described_class.address('D1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb', 'eth')).to be_kind_of(CryptoAddressValidator::Eth)
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'raises UnknownCurrency with unknown currency' do
|
193
|
+
expect { described_class.address('addr', 'asdf') }.to raise_error(CryptoAddressValidator::UnknownCurrency, /asdf/)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'coveralls'
|
4
|
+
Coveralls.wear!
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift File.dirname(__FILE__)
|
7
|
+
require 'rspec'
|
8
|
+
require 'crypto_address_validator'
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
# Enable flags like --only-failures and --next-failure
|
12
|
+
config.example_status_persistence_file_path = '.rspec_status'
|
13
|
+
|
14
|
+
config.order = :random
|
15
|
+
config.filter_run :focus
|
16
|
+
config.run_all_when_everything_filtered = true
|
17
|
+
end
|