adequate_crypto_address 0.1.0 → 0.1.1
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 +4 -4
- data/.coveralls.yml +2 -0
- data/.rubocop.yml +36 -0
- data/.travis.yml +2 -1
- data/Gemfile +3 -2
- data/README.md +5 -6
- data/Rakefile +3 -3
- data/adequate_crypto_address.gemspec +2 -0
- data/bin/console +11 -0
- data/lib/adequate_crypto_address.rb +21 -2
- data/lib/adequate_crypto_address/bch.rb +124 -0
- data/lib/adequate_crypto_address/btc.rb +101 -0
- data/lib/adequate_crypto_address/eth.rb +83 -0
- data/lib/adequate_crypto_address/utils/bch.rb +101 -0
- data/lib/adequate_crypto_address/utils/bech32.rb +166 -0
- data/lib/adequate_crypto_address/version.rb +1 -1
- data/spec/adequate_crypto_address/bch_spec.rb +75 -0
- data/spec/adequate_crypto_address_spec.rb +96 -3
- data/spec/spec_helper.rb +1 -0
- metadata +39 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ef585fbee4de982f4a6dc07dabb0f1673b55c013b90a8994c734dfbc2bad6c73
|
4
|
+
data.tar.gz: 891e4cc96c4aa068be0a5d8d5304e47d925b13a0d72f1a246d156ec4bbdfa993
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b35477ea4b88bf53ba4995760ab6817ec05f9789cb2b952a7778ff590ded8a67f96e6e84f05f65926f4adeda837b3e067ee89af2be3d690178265549259a3265
|
7
|
+
data.tar.gz: 144c2deec22c1e536a0667e7654c0abfe7eb271e02329b27b849f8277aad29067c12878ae751d95bb8261d2c4ef6163e08cd9bbc946f50132816cb5d3874929d
|
data/.coveralls.yml
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
CHANGED
data/Gemfile
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
source
|
1
|
+
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
3
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
4
4
|
|
5
5
|
gem 'coveralls', '>= 0.8.17', require: false
|
6
6
|
gem 'pry', require: false
|
7
7
|
|
8
|
+
gem 'rubocop-rspec'
|
8
9
|
# Specify your gem's dependencies in adequate_crypto_address.gemspec
|
9
10
|
gemspec
|
data/README.md
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
# AdequateCryptoAddress
|
2
2
|
|
3
|
-
[](https://inch-ci.org/github/RubyMoney/money)
|
8
|
-
[](https://gemnasium.com/RubyMoney/money)
|
3
|
+
[](https://rubygems.org/gems/adequate_crypto_address)
|
4
|
+
[](https://travis-ci.org/vtm9/adequate_crypto_address)
|
5
|
+
[](https://codeclimate.com/github/vtm9/adequate_crypto_address)
|
6
|
+
[](https://coveralls.io/r/vtm9/adequate_crypto_address?branch=master)
|
9
7
|
[](https://opensource.org/licenses/MIT)
|
10
8
|
|
11
9
|
|
10
|
+
|
12
11
|
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/adequate_crypto_address`. To experiment with that code, run `bin/console` for an interactive prompt.
|
13
12
|
|
14
13
|
TODO: Delete this and the text above, and describe your gem
|
data/Rakefile
CHANGED
@@ -16,6 +16,8 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
17
17
|
spec.require_paths = ['lib']
|
18
18
|
|
19
|
+
spec.add_dependency 'base58', '~> 0.2.3'
|
20
|
+
spec.add_dependency 'digest-sha3', '~> 1.1.0'
|
19
21
|
spec.add_development_dependency 'bundler', '~> 1.16'
|
20
22
|
spec.add_development_dependency 'rake', '~> 10.0'
|
21
23
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
data/bin/console
ADDED
@@ -1,5 +1,24 @@
|
|
1
|
-
require
|
1
|
+
require 'adequate_crypto_address/utils/bech32'
|
2
|
+
require 'adequate_crypto_address/utils/bch'
|
3
|
+
|
4
|
+
require 'adequate_crypto_address/eth'
|
5
|
+
require 'adequate_crypto_address/btc'
|
6
|
+
require 'adequate_crypto_address/bch'
|
7
|
+
|
2
8
|
|
3
9
|
module AdequateCryptoAddress
|
4
|
-
|
10
|
+
|
11
|
+
module_function
|
12
|
+
|
13
|
+
def valid?(address, currency, type = nil)
|
14
|
+
address(address, currency).valid?(type)
|
15
|
+
end
|
16
|
+
|
17
|
+
def address(address, currency)
|
18
|
+
AdequateCryptoAddress.const_get(currency.capitalize).new(address)
|
19
|
+
end
|
20
|
+
|
21
|
+
def address_type(address, currency)
|
22
|
+
AdequateCryptoAddress.const_get(currency.capitalize).new(address).address_type
|
23
|
+
end
|
5
24
|
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base58'
|
4
|
+
require 'digest'
|
5
|
+
|
6
|
+
module AdequateCryptoAddress
|
7
|
+
class InvalidAddress < StandardError; end
|
8
|
+
class Bch
|
9
|
+
class InvalidLegacyAddress < ::AdequateCryptoAddress::InvalidAddress; end
|
10
|
+
class InvalidCashAddress < ::AdequateCryptoAddress::InvalidAddress; end
|
11
|
+
include ::AdequateCryptoAddress::Utils::Bch
|
12
|
+
|
13
|
+
TYPE_MAP = {
|
14
|
+
legacy: [
|
15
|
+
[:P2SH, 5],
|
16
|
+
[:P2PKH, 0],
|
17
|
+
[:P2SHTestnet, 196],
|
18
|
+
[:P2PKHTestnet, 111]
|
19
|
+
],
|
20
|
+
cash: [
|
21
|
+
[:P2SH, 8],
|
22
|
+
[:P2PKH, 0],
|
23
|
+
[:P2SHTestnet, 8],
|
24
|
+
[:P2PKHTestnet, 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
|
+
puts type
|
39
|
+
puts validated_type
|
40
|
+
type == validated_type.to_sym
|
41
|
+
else
|
42
|
+
!type.nil?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def address_type(address_code, address_type)
|
47
|
+
TYPE_MAP[address_code].each do |mapping|
|
48
|
+
return mapping if mapping.include?(address_type)
|
49
|
+
end
|
50
|
+
|
51
|
+
raise(AdequateCryptoAddress::InvalidAddress, 'Could not determine address type')
|
52
|
+
end
|
53
|
+
|
54
|
+
def legacy_address
|
55
|
+
type_int = address_type(:legacy, type)[1]
|
56
|
+
input = code_list_to_string([type_int] + payload + Array(digest))
|
57
|
+
input += Digest::SHA256.digest(Digest::SHA256.digest(input))[0..3] unless digest
|
58
|
+
Base58.binary_to_base58(input, :bitcoin)
|
59
|
+
end
|
60
|
+
|
61
|
+
def cash_address
|
62
|
+
type_int = address_type(:cash, type)[1]
|
63
|
+
p = [type_int] + payload
|
64
|
+
p = convertbits(p, 8, 5)
|
65
|
+
checksum = calculate_cash_checksum(p)
|
66
|
+
"#{prefix}:#{b32encode(p + checksum)}"
|
67
|
+
end
|
68
|
+
|
69
|
+
alias address cash_address
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def normalize
|
74
|
+
begin
|
75
|
+
from_cash_string
|
76
|
+
rescue InvalidCashAddress
|
77
|
+
from_legacy_string
|
78
|
+
end
|
79
|
+
rescue AdequateCryptoAddress::InvalidAddress
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
|
83
|
+
def from_cash_string
|
84
|
+
if (raw_address.upcase != raw_address) && (raw_address.downcase != raw_address)
|
85
|
+
raise(InvalidCashAddress, 'Cash address contains uppercase and lowercase characters')
|
86
|
+
end
|
87
|
+
|
88
|
+
@raw_address = raw_address.downcase
|
89
|
+
@raw_address = "#{DEFAULT_PREFIX}:#{raw_address}" if !raw_address.include?(':')
|
90
|
+
|
91
|
+
@prefix, base32string = raw_address.split(':')
|
92
|
+
decoded = b32decode(base32string)
|
93
|
+
|
94
|
+
raise(InvalidCashAddress, 'Bad cash address checksum') if !verify_cash_checksum(decoded)
|
95
|
+
|
96
|
+
converted = convertbits(decoded, 5, 8)
|
97
|
+
@type = address_type(:cash, converted[0].to_i)[0]
|
98
|
+
@payload = converted[1..-7]
|
99
|
+
|
100
|
+
@type = :P2SHTestnet if prefix == 'bchtest' && type == :P2SH
|
101
|
+
@type = :P2PKHTestnet if prefix == 'bchtest' && type == :P2PKH
|
102
|
+
end
|
103
|
+
|
104
|
+
def from_legacy_string
|
105
|
+
decoded = nil
|
106
|
+
begin
|
107
|
+
decoded = Base58.base58_to_binary(raw_address, :bitcoin).bytes
|
108
|
+
rescue StandardError
|
109
|
+
raise(InvalidLegacyAddress, 'Could not decode legacy address')
|
110
|
+
end
|
111
|
+
|
112
|
+
@type = address_type(:legacy, decoded[0].to_i)[0]
|
113
|
+
@payload = decoded[1..-5]
|
114
|
+
@digest = decoded[-4..-1]
|
115
|
+
@prefix = DEFAULT_PREFIX
|
116
|
+
|
117
|
+
@type = :P2SHTestnet if prefix == 'bchtest' && type == :P2SH
|
118
|
+
@type = :P2PKHTestnet if prefix == 'bchtest' && type == :P2PKH
|
119
|
+
@prefix = 'bchtest' if [:P2SHTestnet, :P2PKHTestnet].include?(type)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
Bitcoincash = Bch
|
124
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AdequateCryptoAddress
|
4
|
+
class Btc
|
5
|
+
attr_reader :address
|
6
|
+
alias raw_address address
|
7
|
+
|
8
|
+
def initialize(address)
|
9
|
+
@address = address
|
10
|
+
end
|
11
|
+
|
12
|
+
def valid?(type = nil)
|
13
|
+
if type
|
14
|
+
address_type == type.to_sym
|
15
|
+
else
|
16
|
+
!address_type.nil?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def address_type
|
21
|
+
segwit_decoded = begin
|
22
|
+
decode_segwit_address
|
23
|
+
rescue StandardError
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
if segwit_decoded
|
27
|
+
witness_version, witness_program_hex = segwit_decoded
|
28
|
+
witness_program = [witness_program_hex].pack('H*')
|
29
|
+
|
30
|
+
return :segwit_v0_keyhash if witness_version == 0 && witness_program.bytesize == 20
|
31
|
+
|
32
|
+
return :segwit_v0_scripthash if witness_version == 0 && witness_program.bytesize == 32
|
33
|
+
end
|
34
|
+
|
35
|
+
hex = begin
|
36
|
+
decode_base58(address)
|
37
|
+
rescue StandardError
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
if hex && hex.bytesize == 50 && address_checksum?
|
41
|
+
case hex[0...2]
|
42
|
+
when '00'
|
43
|
+
return :hash160
|
44
|
+
when '05'
|
45
|
+
return :p2sh
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def decode_segwit_address
|
55
|
+
actual_hrp, data = Utils::Bech32.decode(address)
|
56
|
+
|
57
|
+
return nil if actual_hrp.nil?
|
58
|
+
|
59
|
+
length = data.size
|
60
|
+
return nil if length == 0 || length > 65
|
61
|
+
return nil if actual_hrp != 'bc'
|
62
|
+
return nil if data[0] > 16
|
63
|
+
|
64
|
+
program = Utils::Bech32.convert_bits(data[1..-1], from_bits: 5, to_bits: 8, pad: false)
|
65
|
+
return nil if program.nil?
|
66
|
+
|
67
|
+
length = program.size
|
68
|
+
return nil if length < 2 || length > 40
|
69
|
+
return nil if data[0] == 0 && length != 20 && length != 32
|
70
|
+
|
71
|
+
program_hex = program.pack('C*').unpack('H*').first
|
72
|
+
[data[0], program_hex]
|
73
|
+
end
|
74
|
+
|
75
|
+
def decode_base58(base58_val)
|
76
|
+
s = Base58.base58_to_int(address, :bitcoin).to_s(16); s = (s.bytesize.odd? ? '0' + s : s)
|
77
|
+
s = '' if s == '00'
|
78
|
+
leading_zero_bytes = (base58_val =~ /^([1]+)/ ? Regexp.last_match(1) : '').size
|
79
|
+
s = ('00' * leading_zero_bytes) + s if leading_zero_bytes > 0
|
80
|
+
s
|
81
|
+
end
|
82
|
+
|
83
|
+
def address_checksum?
|
84
|
+
hex = begin
|
85
|
+
decode_base58(address)
|
86
|
+
rescue StandardError
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
return false unless hex
|
90
|
+
|
91
|
+
checksum(hex[0...42]) == hex[-8..-1]
|
92
|
+
end
|
93
|
+
|
94
|
+
# checksum is a 4 bytes sha256-sha256 hexdigest.
|
95
|
+
def checksum(hex)
|
96
|
+
b = [hex].pack('H*') # unpack hex
|
97
|
+
Digest::SHA256.hexdigest(Digest::SHA256.digest(b))[0...8]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
Bitcoin = Btc
|
101
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AdequateCryptoAddress
|
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::SHA3.new(256).digest(x)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
Ethereum = Eth
|
83
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module AdequateCryptoAddress
|
2
|
+
module Utils
|
3
|
+
module Bch
|
4
|
+
CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'.freeze
|
5
|
+
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def code_list_to_string(code_list)
|
9
|
+
code_list.map { |i| Array(i).pack('C*') }.flatten.join
|
10
|
+
end
|
11
|
+
|
12
|
+
def b32decode(inputs)
|
13
|
+
out = []
|
14
|
+
return out unless inputs
|
15
|
+
|
16
|
+
inputs.split('').each do |letter|
|
17
|
+
out.push(CHARSET.index(letter))
|
18
|
+
end
|
19
|
+
out
|
20
|
+
end
|
21
|
+
|
22
|
+
def polymod(values)
|
23
|
+
chk = 1
|
24
|
+
generator = [
|
25
|
+
[0x01, 0x98f2bc8e61],
|
26
|
+
[0x02, 0x79b76d99e2],
|
27
|
+
[0x04, 0xf33e5fb3c4],
|
28
|
+
[0x08, 0xae2eabe2a8],
|
29
|
+
[0x10, 0x1e4f43e470]
|
30
|
+
]
|
31
|
+
values.each do |value|
|
32
|
+
top = chk >> 35
|
33
|
+
chk = ((chk & 0x07ffffffff) << 5) ^ value
|
34
|
+
generator.each do |i|
|
35
|
+
chk ^= i[1] if (top & i[0]) != 0
|
36
|
+
end
|
37
|
+
end
|
38
|
+
chk ^ 1
|
39
|
+
end
|
40
|
+
|
41
|
+
def expanded_prefix
|
42
|
+
val = if prefix
|
43
|
+
prefix.to_s.split('').map do |i|
|
44
|
+
i.ord & 0x1f
|
45
|
+
end
|
46
|
+
else
|
47
|
+
[]
|
48
|
+
end
|
49
|
+
|
50
|
+
val + [0]
|
51
|
+
end
|
52
|
+
|
53
|
+
def calculate_cash_checksum(payload)
|
54
|
+
poly = polymod(expanded_prefix + payload + [0, 0, 0, 0, 0, 0, 0, 0])
|
55
|
+
out = []
|
56
|
+
8.times do |i|
|
57
|
+
out.push((poly >> 5 * (7 - i)) & 0x1f)
|
58
|
+
end
|
59
|
+
out
|
60
|
+
end
|
61
|
+
|
62
|
+
def verify_cash_checksum(payload)
|
63
|
+
polymod(expanded_prefix + payload) == 0
|
64
|
+
rescue TypeError
|
65
|
+
raise AdequateCryptoAddress::InvalidAddress
|
66
|
+
end
|
67
|
+
|
68
|
+
def b32encode(inputs)
|
69
|
+
out = ''
|
70
|
+
inputs.each do |char_code|
|
71
|
+
out += CHARSET[char_code].to_s
|
72
|
+
end
|
73
|
+
out
|
74
|
+
end
|
75
|
+
|
76
|
+
def convertbits(data, frombits, tobits, pad = true)
|
77
|
+
acc = 0
|
78
|
+
bits = 0
|
79
|
+
ret = []
|
80
|
+
maxv = (1 << tobits) - 1
|
81
|
+
max_acc = (1 << (frombits + tobits - 1)) - 1
|
82
|
+
data.each do |value|
|
83
|
+
return nil if value < 0 || ((value >> frombits) != 0)
|
84
|
+
|
85
|
+
acc = ((acc << frombits) | value) & max_acc
|
86
|
+
bits += frombits
|
87
|
+
while bits >= tobits
|
88
|
+
bits -= tobits
|
89
|
+
ret.push((acc >> bits) & maxv)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
if pad
|
93
|
+
ret.push((acc << (tobits - bits)) & maxv) if bits != 0
|
94
|
+
elsif bits >= frombits || (((acc << (tobits - bits)) & maxv) != 0)
|
95
|
+
return nil
|
96
|
+
end
|
97
|
+
ret
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
module AdequateCryptoAddress
|
2
|
+
# Ruby reference implementation: https://github.com/sipa/bech32/tree/master/ref/c
|
3
|
+
module Utils
|
4
|
+
module Bech32
|
5
|
+
CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'.unpack('C*')
|
6
|
+
CHARSET_REV = [
|
7
|
+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
8
|
+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
9
|
+
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
10
|
+
15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1,
|
11
|
+
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
|
12
|
+
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -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
|
+
].freeze
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def polymod_step(pre)
|
19
|
+
b = pre >> 25
|
20
|
+
((pre & 0x1FFFFFF) << 5) ^ \
|
21
|
+
(-((b >> 0) & 1) & 0x3b6a57b2) ^ \
|
22
|
+
(-((b >> 1) & 1) & 0x26508e6d) ^ \
|
23
|
+
(-((b >> 2) & 1) & 0x1ea119fa) ^ \
|
24
|
+
(-((b >> 3) & 1) & 0x3d4233dd) ^ \
|
25
|
+
(-((b >> 4) & 1) & 0x2a1462b3)
|
26
|
+
end
|
27
|
+
|
28
|
+
# def encode(hrp, data)
|
29
|
+
# buf = []
|
30
|
+
# chk = 1
|
31
|
+
|
32
|
+
# hrp.unpack('C*').each do |ch|
|
33
|
+
# return nil if ch < 33 || ch > 126
|
34
|
+
# return nil if ch >= 'A'.ord && ch <= 'Z'.ord
|
35
|
+
|
36
|
+
# chk = polymod_step(chk) ^ (ch >> 5)
|
37
|
+
# end
|
38
|
+
|
39
|
+
# return nil if (hrp.bytesize + 7 + data.size) > 90
|
40
|
+
|
41
|
+
# chk = polymod_step(chk)
|
42
|
+
# hrp.unpack('C*').each do |ch|
|
43
|
+
# chk = polymod_step(chk) ^ (ch & 0x1f)
|
44
|
+
# buf << ch
|
45
|
+
# end
|
46
|
+
|
47
|
+
# buf << '1'.ord
|
48
|
+
|
49
|
+
# data.each do |i|
|
50
|
+
# return nil if (i >> 5) != 0
|
51
|
+
|
52
|
+
# chk = polymod_step(chk) ^ i
|
53
|
+
# buf << CHARSET[i]
|
54
|
+
# end
|
55
|
+
|
56
|
+
# 6.times do
|
57
|
+
# chk = polymod_step(chk)
|
58
|
+
# end
|
59
|
+
|
60
|
+
# chk ^= 1
|
61
|
+
|
62
|
+
# 6.times do |i|
|
63
|
+
# buf << CHARSET[(chk >> ((5 - i) * 5)) & 0x1f]
|
64
|
+
# end
|
65
|
+
|
66
|
+
# buf.pack('C*')
|
67
|
+
# end
|
68
|
+
|
69
|
+
# rubocop:disable CyclomaticComplexity,PerceivedComplexity
|
70
|
+
def decode(input)
|
71
|
+
chk = 1
|
72
|
+
input_len = input.bytesize
|
73
|
+
have_lower = false
|
74
|
+
have_upper = false
|
75
|
+
|
76
|
+
return nil if input_len < 8 || input_len > 90
|
77
|
+
|
78
|
+
data_len = 0
|
79
|
+
data_len += 1 while data_len < input_len && input[(input_len - 1) - data_len] != '1'
|
80
|
+
|
81
|
+
hrp_len = input_len - (1 + data_len)
|
82
|
+
return nil if hrp_len < 1 || data_len < 6
|
83
|
+
|
84
|
+
hrp = []
|
85
|
+
hrp_len.times do |i|
|
86
|
+
ch = input[i].ord
|
87
|
+
return nil if ch < 33 || ch > 126
|
88
|
+
|
89
|
+
if ch >= 'a'.ord && ch <= 'z'.ord
|
90
|
+
have_lower = true
|
91
|
+
elsif ch >= 'A'.ord && ch <= 'Z'.ord
|
92
|
+
have_upper = true
|
93
|
+
ch = (ch - 'A'.ord) + 'a'.ord
|
94
|
+
end
|
95
|
+
|
96
|
+
hrp << ch
|
97
|
+
chk = polymod_step(chk) ^ (ch >> 5)
|
98
|
+
end
|
99
|
+
|
100
|
+
chk = polymod_step(chk)
|
101
|
+
|
102
|
+
hrp_len.times do |i|
|
103
|
+
chk = polymod_step(chk) ^ (input[i].ord & 0x1f)
|
104
|
+
end
|
105
|
+
|
106
|
+
data = []
|
107
|
+
i = hrp_len + 1
|
108
|
+
while i < input_len
|
109
|
+
ch = input[i].ord
|
110
|
+
v = (ch & 0x80) != 0 ? -1 : CHARSET_REV[ch]
|
111
|
+
|
112
|
+
have_lower = true if ch >= 'a'.ord && ch <= 'z'.ord
|
113
|
+
have_upper = true if ch >= 'A'.ord && ch <= 'Z'.ord
|
114
|
+
return nil if v == -1
|
115
|
+
|
116
|
+
chk = polymod_step(chk) ^ v
|
117
|
+
data << v if (i + 6) < input_len
|
118
|
+
i += 1
|
119
|
+
end
|
120
|
+
|
121
|
+
return nil if have_lower && have_upper
|
122
|
+
return nil if chk != 1
|
123
|
+
|
124
|
+
[hrp.pack('C*'), data]
|
125
|
+
end
|
126
|
+
# rubocop:enable CyclomaticComplexity,PerceivedComplexity
|
127
|
+
|
128
|
+
# Utility for converting bytes of data between bases. These is used for
|
129
|
+
# BIP 173 address encoding/decoding to convert between sequences of bytes
|
130
|
+
# representing 8-bit values and groups of 5 bits. Conversions may be padded
|
131
|
+
# with trailing 0 bits to the nearest byte boundary. Returns nil if
|
132
|
+
# conversion requires padding and pad is false.
|
133
|
+
#
|
134
|
+
# For example:
|
135
|
+
#
|
136
|
+
# convert_bits("\xFF\xFF", from_bits: 8, to_bits: 5, pad: true)
|
137
|
+
# => "\x1F\x1F\x1F\10"
|
138
|
+
#
|
139
|
+
# See https://github.com/bitcoin/bitcoin/blob/595a7bab23bc21049526229054ea1fff1a29c0bf/src/utilstrencodings.h#L154
|
140
|
+
def convert_bits(chunks, from_bits:, to_bits:, pad:)
|
141
|
+
output_mask = (1 << to_bits) - 1
|
142
|
+
buffer_mask = (1 << (from_bits + to_bits - 1)) - 1
|
143
|
+
|
144
|
+
buffer = 0
|
145
|
+
bits = 0
|
146
|
+
|
147
|
+
output = []
|
148
|
+
chunks.each do |chunk|
|
149
|
+
buffer = ((buffer << from_bits) | chunk) & buffer_mask
|
150
|
+
bits += from_bits
|
151
|
+
while bits >= to_bits
|
152
|
+
bits -= to_bits
|
153
|
+
output << ((buffer >> bits) & output_mask)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
output << ((buffer << (to_bits - bits)) & output_mask) if pad && bits > 0
|
158
|
+
|
159
|
+
return nil if !pad && (bits >= from_bits || ((buffer << (to_bits - bits)) & output_mask) != 0)
|
160
|
+
|
161
|
+
output
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
RSpec.describe(AdequateCryptoAddress::Bch) do
|
2
|
+
let(:legacy_p2sh) { '3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC' }
|
3
|
+
let(:legacy_p2pkh) { '155fzsEBHy9Ri2bMQ8uuuR3tv1YzcDywd4' }
|
4
|
+
let(:cashaddr_p2sh) { 'bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq' }
|
5
|
+
let(:cashaddr_p2pkh) { 'bitcoincash:qqkv9wr69ry2p9l53lxp635va4h86wv435995w8p2h' }
|
6
|
+
let(:cashaddr_p2pkh_testnet) { 'bchtest:qpqtmmfpw79thzq5z7s0spcd87uhn6d34uqqem83hf' }
|
7
|
+
let(:legacy_p2pkh_testnet) { 'mmRH4e9WW4ekZUP5HvBScfUyaSUjfQRyvD' }
|
8
|
+
let(:cashaddr_p2sh_testnet) { 'bchtest:pp8f7ww2g6y07ypp9r4yendrgyznysc9kqxh6acwu3' }
|
9
|
+
let(:legacy_p2sh_testnet) { '2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc' }
|
10
|
+
let(:mixed_case_cashadddr) { 'bitcoincash:qqkv9wr69ry2p9l53lxP635va4h86wv435995w8p2H' }
|
11
|
+
|
12
|
+
describe '#legacy_address' do
|
13
|
+
it 'converts legacy testnet p2pkh' do
|
14
|
+
expect(described_class.new(legacy_p2pkh_testnet).legacy_address).to eq(legacy_p2pkh_testnet)
|
15
|
+
expect(described_class.new(cashaddr_p2pkh_testnet).legacy_address).to eq(legacy_p2pkh_testnet)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'converts legacy testnet p2sh' do
|
19
|
+
expect(described_class.new(legacy_p2sh_testnet).legacy_address).to eq(legacy_p2sh_testnet)
|
20
|
+
expect(described_class.new(cashaddr_p2sh_testnet).legacy_address).to eq(legacy_p2sh_testnet)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'converts to legacy p2sh' do
|
24
|
+
expect(described_class.new(legacy_p2sh).legacy_address).to eq(legacy_p2sh)
|
25
|
+
expect(described_class.new(cashaddr_p2sh).legacy_address).to eq(legacy_p2sh)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'converts to legacy p2pkh' do
|
29
|
+
expect(described_class.new(legacy_p2pkh).legacy_address).to eq(legacy_p2pkh)
|
30
|
+
expect(described_class.new(cashaddr_p2pkh).legacy_address).to eq(legacy_p2pkh)
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'uppercase cashaddr address' do
|
34
|
+
it 'converts to legacy p2sh' do
|
35
|
+
expect(described_class.new(cashaddr_p2sh.upcase).legacy_address).to eq(legacy_p2sh)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'converts to legacy p2pkh' do
|
39
|
+
expect(described_class.new(cashaddr_p2pkh.upcase).legacy_address).to eq(legacy_p2pkh)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context '#cash_address' do
|
45
|
+
it 'converts cash testnet p2pkh' do
|
46
|
+
expect(described_class.new(legacy_p2pkh_testnet).cash_address).to eq(cashaddr_p2pkh_testnet)
|
47
|
+
expect(described_class.new(cashaddr_p2pkh_testnet).cash_address).to eq(cashaddr_p2pkh_testnet)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'converts cash testnet p2sh' do
|
51
|
+
expect(described_class.new(legacy_p2sh_testnet).cash_address).to eq(cashaddr_p2sh_testnet)
|
52
|
+
expect(described_class.new(cashaddr_p2sh_testnet).cash_address).to eq(cashaddr_p2sh_testnet)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'converts to cash p2sh' do
|
56
|
+
expect(described_class.new(legacy_p2sh).cash_address).to eq(cashaddr_p2sh)
|
57
|
+
expect(described_class.new(cashaddr_p2sh).cash_address).to eq(cashaddr_p2sh)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'converts to cash p2pkh' do
|
61
|
+
expect(described_class.new(legacy_p2pkh).cash_address).to eq(cashaddr_p2pkh)
|
62
|
+
expect(described_class.new(cashaddr_p2pkh).cash_address).to eq(cashaddr_p2pkh)
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'uppercase cashaddr address' do
|
66
|
+
it 'converts to cash p2sh' do
|
67
|
+
expect(described_class.new(cashaddr_p2sh.upcase).cash_address).to eq(cashaddr_p2sh)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'converts to cash p2pkh' do
|
71
|
+
expect(described_class.new(cashaddr_p2pkh.upcase).cash_address).to eq(cashaddr_p2pkh)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -1,5 +1,98 @@
|
|
1
|
-
RSpec.describe
|
2
|
-
|
3
|
-
|
1
|
+
RSpec.describe(AdequateCryptoAddress) do
|
2
|
+
describe '.valid?' do
|
3
|
+
context 'Bitcoin' do
|
4
|
+
it 'validates hash160 addresses' do
|
5
|
+
expect(described_class).to be_valid('12KYrjTdVGjFMtaxERSk3gphreJ5US8aUP', 'bitcoin')
|
6
|
+
expect(described_class).to be_valid('12QeMLzSrB8XH8FvEzPMVoRxVAzTr5XM2y', 'BTC')
|
7
|
+
expect(described_class).to be_valid('12QeMLzSrB8XH8FvEzPMVoRxVAzTr5XM2y', 'Bitcoin')
|
8
|
+
expect(described_class).to be_valid('12QeMLzSrB8XH8FvEzPMVoRxVAzTr5XM2y', 'btc')
|
9
|
+
expect(described_class).to be_valid('12QeMLzSrB8XH8FvEzPMVoRxVAzTr5XM2y', 'btc', :hash160)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'validates p2sh addresses' do
|
13
|
+
expect(described_class).to be_valid('3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt', 'BTC')
|
14
|
+
expect(described_class).to be_valid('3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt', 'bitcoin', 'p2sh')
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'validates segwit addresses' do
|
18
|
+
expect(described_class).to be_valid('BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4', 'BTC')
|
19
|
+
expect(described_class).to be_valid('bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq', 'bitcoin')
|
20
|
+
expect(described_class).to be_valid('bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq', 'bitcoin', 'segwit_v0_keyhash')
|
21
|
+
expect(described_class).to be_valid('bc1qc7slrfxkknqcq2jevvvkdgvrt8080852dfjewde450xdlk4ugp7szw5tk9', 'BTC')
|
22
|
+
expect(described_class).to be_valid('bc1qc7slrfxkknqcq2jevvvkdgvrt8080852dfjewde450xdlk4ugp7szw5tk9', 'bitcoin', 'segwit_v0_scripthash')
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'validates wrong addresses' do
|
26
|
+
expect(described_class).not_to be_valid('asdf', :bitcoin)
|
27
|
+
expect(described_class).not_to be_valid('3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt', 'bitcoin', :segwit_v0_keyhash)
|
28
|
+
expect(described_class).not_to be_valid('3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt', 'bitcoin', 'asdf')
|
29
|
+
expect(described_class).not_to be_valid('tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty', :bitcoin)
|
30
|
+
expect(described_class).not_to be_valid('bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5', 'bitcoin')
|
31
|
+
expect(described_class).not_to be_valid('BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2', 'bitcoin')
|
32
|
+
expect(described_class).not_to be_valid('bc1rw5uspcuh', 'bitcoin')
|
33
|
+
expect(described_class).not_to be_valid('bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90', 'bitcoin')
|
34
|
+
expect(described_class).not_to be_valid('BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P', 'bitcoin')
|
35
|
+
expect(described_class).not_to be_valid('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7', 'BTC')
|
36
|
+
expect(described_class).not_to be_valid('bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du', 'bitcoin')
|
37
|
+
expect(described_class).not_to be_valid('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv', 'Bitcoin')
|
38
|
+
expect(described_class).not_to be_valid('bc1gmk9yu', 'bitcoin')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'Bitcoincash' do
|
43
|
+
it 'validates legacy addresses' do
|
44
|
+
expect(described_class).to be_valid('3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC', :bch, :P2SH)
|
45
|
+
expect(described_class).to be_valid('155fzsEBHy9Ri2bMQ8uuuR3tv1YzcDywd4', 'bitcoincash', :P2PKH)
|
46
|
+
expect(described_class).to be_valid('2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc', 'BCH', :P2SHTestnet)
|
47
|
+
expect(described_class).to be_valid('mmRH4e9WW4ekZUP5HvBScfUyaSUjfQRyvD', :BCH, :P2PKHTestnet)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'validates cash addresses' do
|
51
|
+
expect(described_class).to be_valid('bitcoincash:qqkv9wr69ry2p9l53lxp635va4h86wv435995w8p2h', :bch, :P2PKH)
|
52
|
+
expect(described_class).to be_valid('bitcoincash:pqdg9uq52wzhf228hweext9j2jdjgdpj9qt7xxfngd', :bitcoincash, :P2SH)
|
53
|
+
expect(described_class).to be_valid('bchtest:qpqtmmfpw79thzq5z7s0spcd87uhn6d34uqqem83hf', :Bitcoincash, :P2PKHTestnet)
|
54
|
+
expect(described_class).to be_valid('bchtest:pp8f7ww2g6y07ypp9r4yendrgyznysc9kqxh6acwu3', :BCH, :P2SHTestnet)
|
55
|
+
expect(described_class).to be_valid('bitcoincash:qrtj3rd8524cndt2eew3s6wljqggmne00sgh4kfypk', :bitcoincash)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'validates cash addresses without prefix addresses' do
|
59
|
+
expect(described_class).to be_valid('qrtj3rd8524cndt2eew3s6wljqggmne00sgh4kfypk', :bitcoincash)
|
60
|
+
expect(described_class).to be_valid('pqdg9uq52wzhf228hweext9j2jdjgdpj9qt7xxfngd', :bitcoincash, :P2SH)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'validates wrong addresses' do
|
64
|
+
expect(described_class).not_to be_valid('bitcoincash:qqkv9wr69ry2p9l53lxP635va4h86wv435995w8p2H', :bch)
|
65
|
+
expect(described_class).not_to be_valid('wrong', :bch)
|
66
|
+
expect(described_class).not_to be_valid('bitcoincash:wrong', :bch)
|
67
|
+
expect(described_class).not_to be_valid('bitcoincash:123', :bch)
|
68
|
+
|
69
|
+
expect(described_class).not_to be_valid('bitcoincash:qqkv9wr69ry2p9l53lxp635va4h86wv435995w8p2h', :bch, :P2PKHTestnet)
|
70
|
+
expect(described_class).not_to be_valid('bitcoincash:pqdg9uq52wzhf228hweext9j2jdjgdpj9qt7xxfngd', :bitcoincash, :P2PKHTestnet)
|
71
|
+
expect(described_class).not_to be_valid('bchtest:qpqtmmfpw79thzq5z7s0spcd87uhn6d34uqqem83hf', :Bitcoincash, :P2SH)
|
72
|
+
expect(described_class).not_to be_valid('bchtest:pp8f7ww2g6y07ypp9r4yendrgyznysc9kqxh6acwu3', :BCH, :P2SH)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'Ethereum' do
|
77
|
+
it 'validates addresses' do
|
78
|
+
expect(described_class).to be_valid('0xE37c0D48d68da5c5b14E5c1a9f1CFE802776D9FF', 'ethereum')
|
79
|
+
expect(described_class).to be_valid('0xa00354276d2fC74ee91e37D085d35748613f4748', :ethereum)
|
80
|
+
expect(described_class).to be_valid('0xAff4d6793F584a473348EbA058deb8caad77a288', :ETH)
|
81
|
+
expect(described_class).to be_valid('0xc6d9d2cd449a754c494264e1809c50e34d64562b', 'ETH')
|
82
|
+
expect(described_class).to be_valid('0x52908400098527886E0F7030069857D2E4169EE7', 'ETH')
|
83
|
+
expect(described_class).to be_valid('0x8617E340B3D01FA5F11F306F4090FD50E238070D', 'ETH')
|
84
|
+
expect(described_class).to be_valid('0xde709f2102306220921060314715629080e2fb77', 'ETH')
|
85
|
+
expect(described_class).to be_valid('0x27b1fdb04752bbc536007a920d24acb045561c26', 'ETH')
|
86
|
+
expect(described_class).to be_valid('0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed', 'ETH')
|
87
|
+
expect(described_class).to be_valid('0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359', 'ETH')
|
88
|
+
expect(described_class).to be_valid('0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB', 'ETH')
|
89
|
+
expect(described_class).to be_valid('0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb', 'ETH')
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'validates wrong addresses' do
|
93
|
+
expect(described_class).not_to be_valid('0xD1110A0cf47c7B9Be7A2E6BA89F429762e7b9aDb', 'ETH')
|
94
|
+
expect(described_class).not_to be_valid('0xa10354276d2fC74ee91e37D085d35748613f4748', :ethereum)
|
95
|
+
end
|
96
|
+
end
|
4
97
|
end
|
5
98
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: adequate_crypto_address
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- vtm
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-09-
|
11
|
+
date: 2018-09-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: base58
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.2.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.2.3
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: digest-sha3
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.1.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.1.0
|
13
41
|
- !ruby/object:Gem::Dependency
|
14
42
|
name: bundler
|
15
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -59,15 +87,24 @@ executables: []
|
|
59
87
|
extensions: []
|
60
88
|
extra_rdoc_files: []
|
61
89
|
files:
|
90
|
+
- ".coveralls.yml"
|
62
91
|
- ".gitignore"
|
63
92
|
- ".rspec"
|
93
|
+
- ".rubocop.yml"
|
64
94
|
- ".travis.yml"
|
65
95
|
- Gemfile
|
66
96
|
- README.md
|
67
97
|
- Rakefile
|
68
98
|
- adequate_crypto_address.gemspec
|
99
|
+
- bin/console
|
69
100
|
- lib/adequate_crypto_address.rb
|
101
|
+
- lib/adequate_crypto_address/bch.rb
|
102
|
+
- lib/adequate_crypto_address/btc.rb
|
103
|
+
- lib/adequate_crypto_address/eth.rb
|
104
|
+
- lib/adequate_crypto_address/utils/bch.rb
|
105
|
+
- lib/adequate_crypto_address/utils/bech32.rb
|
70
106
|
- lib/adequate_crypto_address/version.rb
|
107
|
+
- spec/adequate_crypto_address/bch_spec.rb
|
71
108
|
- spec/adequate_crypto_address_spec.rb
|
72
109
|
- spec/spec_helper.rb
|
73
110
|
homepage: https://github.com/vtm9/adequate_crypto_address
|