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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6302b9458662b0023b448f264c8ad93849200fe2a390d6db50fe194bdbbab8bb
4
- data.tar.gz: bfac4cfc3eb5efa9e8f8ff20ab80215553e12ff5cd1a36829253291e1161b827
3
+ metadata.gz: ef585fbee4de982f4a6dc07dabb0f1673b55c013b90a8994c734dfbc2bad6c73
4
+ data.tar.gz: 891e4cc96c4aa068be0a5d8d5304e47d925b13a0d72f1a246d156ec4bbdfa993
5
5
  SHA512:
6
- metadata.gz: 594bcbf0e964583725d9f8d89f1e1bcd17967304b5fad5134373ed696858a341c5cefe2b2e70a632619c5257f4f6504dbbb2658ce1327c21368814e48f67f8a5
7
- data.tar.gz: c071bf3df98d6b06f07b7b4efada8bc8bf6b084d749c07ff8547ca67cdb0bf2f7cbe56884bebb432e9e534cae400796ff25760ae0c9631347f52d80274e3a4a3
6
+ metadata.gz: b35477ea4b88bf53ba4995760ab6817ec05f9789cb2b952a7778ff590ded8a67f96e6e84f05f65926f4adeda837b3e067ee89af2be3d690178265549259a3265
7
+ data.tar.gz: 144c2deec22c1e536a0667e7654c0abfe7eb271e02329b27b849f8277aad29067c12878ae751d95bb8261d2c4ef6163e08cd9bbc946f50132816cb5d3874929d
data/.coveralls.yml ADDED
@@ -0,0 +1,2 @@
1
+ repo_token: ATgUXu3s0jZaYdzAdUr8P8ytuMMbR9ueh
2
+
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
@@ -9,4 +9,5 @@ script: bundle exec rspec spec
9
9
  notifications:
10
10
  email:
11
11
  - vtmilyakov@yandex.ru
12
-
12
+ after_success:
13
+ - CI=true TRAVIS=true coveralls --verbose
data/Gemfile CHANGED
@@ -1,9 +1,10 @@
1
- source "https://rubygems.org"
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
- [![Gem Version](https://badge.fury.io/rb/money.svg)](https://rubygems.org/gems/money)
4
- [![Build Status](https://travis-ci.org/RubyMoney/money.svg?branch=master)](https://travis-ci.org/RubyMoney/money)
5
- [![Code Climate](https://codeclimate.com/github/RubyMoney/money.svg)](https://codeclimate.com/github/RubyMoney/money)
6
- [![Coverage Status](https://coveralls.io/repos/RubyMoney/money/badge.svg?branch=master)](https://coveralls.io/r/RubyMoney/money?branch=master)
7
- [![Inline docs](https://inch-ci.org/github/RubyMoney/money.svg)](https://inch-ci.org/github/RubyMoney/money)
8
- [![Dependency Status](https://gemnasium.com/RubyMoney/money.svg)](https://gemnasium.com/RubyMoney/money)
3
+ [![Gem Version](https://badge.fury.io/rb/adequate_crypto_address.svg)](https://rubygems.org/gems/adequate_crypto_address)
4
+ [![Build Status](https://travis-ci.org/vtm9/adequate_crypto_address.svg?branch=master)](https://travis-ci.org/vtm9/adequate_crypto_address)
5
+ [![Code Climate](https://codeclimate.com/github/vtm9/adequate_crypto_address.svg)](https://codeclimate.com/github/vtm9/adequate_crypto_address)
6
+ [![Coverage Status](https://coveralls.io/repos/vtm9/adequate_crypto_address/badge.svg?branch=master)](https://coveralls.io/r/vtm9/adequate_crypto_address?branch=master)
9
7
  [![License](https://img.shields.io/github/license/RubyMoney/money.svg)](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
@@ -1,6 +1,6 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
- task :default => :spec
6
+ task default: :spec
@@ -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
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift( File.expand_path("../../lib", __FILE__) )
4
+ require 'adequate_crypto_address'
5
+
6
+ ACA = AdequateCryptoAddress
7
+ include AdequateCryptoAddress
8
+
9
+ require 'pry'
10
+ Pry.start
11
+
@@ -1,5 +1,24 @@
1
- require "adequate_crypto_address/version"
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
- # Your code goes here...
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
@@ -1,3 +1,3 @@
1
1
  module AdequateCryptoAddress
2
- VERSION = '0.1.0'.freeze
2
+ VERSION = '0.1.1'.freeze
3
3
  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 AdequateCryptoAddress do
2
- it 'has a version number' do
3
- expect(AdequateCryptoAddress::VERSION).not_to be nil
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
@@ -1,6 +1,7 @@
1
1
  require 'coveralls'
2
2
  Coveralls.wear!
3
3
 
4
+ $LOAD_PATH.unshift File.dirname(__FILE__)
4
5
  require 'rspec'
5
6
  require 'adequate_crypto_address'
6
7
 
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.0
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-14 00:00:00.000000000 Z
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