adequate_crypto_address 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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