adequate_crypto_address 0.1.11 → 0.3.0

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: 6fee1d177fb87fdb5e97bb27ac01606349f567b99bd1d7f1a2553c8d6a17e1de
4
- data.tar.gz: 7968e28889bc532c2705322a5d15ddeaf386c82f13fc41114bca6df0d375c49f
3
+ metadata.gz: df433b807d749cf8b119f5518093375a5634c1c1669ca7414ddb55085bc1f08b
4
+ data.tar.gz: 3dc0e213e7fcbc06b82cb0062af5c80caa3d56466ac67e8689c20fa48f01a952
5
5
  SHA512:
6
- metadata.gz: 62df3006c3a130904b82d42cad6b94259fc6461dbdcbae0f0e0227716fccf0200aa6d6b698c7ae76ffde13fc85b23df993f20ddd2444f8a6e12648320c051f2f
7
- data.tar.gz: bd5e6981b62cd15be7ccac5c9604865fa272d9bec754f2ba8ef4ed0dff9b6c3d4f6d1c31648b14c4cb7b64e244869a3b8808c6bf851b72a84c2cc2362ccd20bf
6
+ metadata.gz: 358d49195d75675e1364617c1053795fc31e817534701e0758f2086653ef2e6aa11fc6198f596216fe5228dae1d73a73fd4b02c18b9d96ecdc4a14cb415230c2
7
+ data.tar.gz: 36a5d580408b7063de6765f031aaaeb2e9d3f2f98dcf8296bbdf314a8d4c37deadaacf271ffb458ba5edebfb8cebc270e1f030565e11682d704b7ed1125dd265
data/CHANGELOG.md ADDED
@@ -0,0 +1,86 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.3.0] - 2026-09-02
9
+
10
+ ### Added
11
+
12
+ - **TRON (TRX)** validation — Base58Check with the `0x41` version byte and
13
+ double-SHA256 checksum. Currency names: `trx`, `tron`. Type: `:prod`.
14
+ - **Tezos (XTZ)** validation — Base58Check with `tz1`/`tz2`/`tz3` implicit and
15
+ `KT1` originated (contract) prefixes. Currency names: `xtz`, `tezos`.
16
+ Types: `:implicit`, `:originated`.
17
+ - **Cosmos (ATOM)** validation — Bech32 with the `cosmos` human-readable part and
18
+ a 20-byte payload. Currency names: `atom`, `cosmos`. Type: `:prod`.
19
+ - **BNB Smart Chain (BSC)** validation — Ethereum-format addresses with the
20
+ EIP-55 checksum. Currency names: `bsc`, `binancesmartchain`. Type: `:bsc`.
21
+
22
+ [0.3.0]: https://github.com/vtm9/adequate_crypto_address/releases/tag/v0.3.0
23
+
24
+ ## [0.2.0] - 2026-09-02
25
+
26
+ This release hardens the validators against malformed input found in the
27
+ September 2026 security review. Several addresses that previous versions
28
+ incorrectly accepted are now rejected, and the public `address_type` API is
29
+ consistent across every currency. See the "Upgrading from 0.1.x to 0.2.0"
30
+ section of the README for migration guidance.
31
+
32
+ ### Security
33
+
34
+ - **BCH legacy Base58Check** now verifies the four-byte double-SHA256 checksum
35
+ and requires the exact 25-byte decoded length. One-character checksum
36
+ mutations are rejected.
37
+ - **BCH CashAddr** now accepts only the `bitcoincash`, `bchtest`, and `bchreg`
38
+ prefixes (an `evil:` prefix with a valid checksum is rejected) and validates
39
+ the reserved bit, type bits, declared hash size, and decoded payload length
40
+ (a shortened payload with a recomputed checksum is rejected).
41
+ - **TON** addresses are fully decoded: Base64URL payload, tag, workchain,
42
+ 32-byte account ID, CRC16-CCITT checksum, and testnet flag. Arbitrary
43
+ 48-character strings such as `"A" * 48` are no longer accepted.
44
+ - **Monero** is validated by decoding the Monero Base58 payload and checking the
45
+ network byte, structural length, and Keccak-256 checksum for standard,
46
+ integrated, and subaddress forms. Non-Base58 input such as `"4" + "!" * 94`
47
+ is rejected.
48
+ - **Bounded input**: all validators enforce a practical length cap before
49
+ Base58/Bech32 decoding, preventing pathological slowdowns on oversized input.
50
+ - Currency resolution now uses an explicit allowlist, so a `NoMethodError` from
51
+ malformed input can no longer be misreported as `UnknownCurrency`.
52
+
53
+ ### Added
54
+
55
+ - Public `AdequateCryptoAddress.address_type(address, currency)` with a
56
+ consistent contract: it returns the detected type as a `Symbol` when the
57
+ address is valid, or `nil` when it is not, for every supported currency.
58
+ - `Btc#network` and `Xmr#network` expose `:mainnet`/`:testnet`
59
+ (`:stagenet` for Monero) so callers can enforce a network.
60
+ - `Toncoin` alias for the `Ton` validator.
61
+ - `bundler-audit` dependency advisory check, wired into `mise run check` and CI.
62
+ - Regression specs for checksum mutations, oversized input, nil input, invalid
63
+ currency names, and the `address_type` contract across all currencies.
64
+
65
+ ### Changed
66
+
67
+ - **Breaking:** `address_type` is now public on every validator and returns a
68
+ type `Symbol` or `nil`. Previously it was private on most validators, returned
69
+ `nil` for ETH/SOL/XLM, and required arguments on BCH.
70
+ - **Breaking:** TON's detected type changed from `:TON` to `:ton_mainnet` /
71
+ `:ton_testnet`.
72
+ - **Breaking:** Monero's detected type is now `:standard`, `:integrated`, or
73
+ `:subaddress` (was `:monero`).
74
+ - ETH now reports `address_type` `:eth`, SOL reports `:solana`, and XLM reports
75
+ `:account` / `:muxed`.
76
+ - The BCH internal type-mapping helper was renamed from `address_type(code, type)`
77
+ to `type_mapping(code, type)`.
78
+
79
+ ### Documentation
80
+
81
+ - Documented supported per-currency types, the `address_type` API, network
82
+ accessors, and an explicit "Format coverage" section listing address formats
83
+ that are intentionally out of scope (and rejected rather than silently
84
+ accepted).
85
+
86
+ [0.2.0]: https://github.com/vtm9/adequate_crypto_address/releases/tag/v0.2.0
data/README.md CHANGED
@@ -4,7 +4,6 @@ AdequateCryptoAddress
4
4
 
5
5
  [![Gem Version][gem-version-svg]][gem-version-link]
6
6
  [![Build Status][build-status-svg]][build-status-link]
7
- [![Code Climate][codeclimate-status-svg]][codeclimate-status-link]
8
7
  [![Coverage Status][coverage-status-svg]][coverage-status-link]
9
8
  [![Downloads][downloads-svg]][downloads-link]
10
9
  [![Docs][docs-rubydoc-svg]][docs-rubydoc-link]
@@ -29,27 +28,136 @@ Or install it yourself as:
29
28
  gem install adequate_crypto_address
30
29
  ```
31
30
 
31
+ ## Upgrading from 0.1.x to 0.2.0
32
+
33
+ 0.2.0 hardens the validators against malformed input and makes the
34
+ `address_type` API consistent. Most callers of `.valid?` need no changes, but a
35
+ few behaviors changed. See the [CHANGELOG](CHANGELOG.md) for the full list.
36
+
37
+ ### Stricter validation (addresses that used to pass may now fail)
38
+
39
+ Some malformed addresses that earlier versions incorrectly accepted are now
40
+ rejected. If your app stored or allow-listed such values, re-validate them:
41
+
42
+ - **BCH** legacy addresses with a bad checksum/length, CashAddr addresses with a
43
+ non-`bitcoincash`/`bchtest`/`bchreg` prefix (e.g. `evil:...`), or a payload
44
+ shorter than the version byte declares.
45
+ - **TON** any 48-character string (e.g. `"A" * 48`) — a valid tag, workchain,
46
+ and CRC16 checksum are now required.
47
+ - **Monero** anything that is not a checksum-valid Base58 address (e.g.
48
+ `"4" + "!" * 94`).
49
+
50
+ If you rely on a currency name, note that unknown names still raise
51
+ `AdequateCryptoAddress::UnknownCurrency`, but a `nil` or malformed **address**
52
+ now consistently returns `false` from `.valid?` instead of raising.
53
+
54
+ ### `address_type` is now public and consistent
55
+
56
+ `AdequateCryptoAddress.address_type(address, currency)` is a supported public
57
+ method for every currency. It returns the detected type as a `Symbol` when the
58
+ address is valid, or `nil` when it is not.
59
+
60
+ Some returned type symbols changed. Update any code that compared against the
61
+ old values:
62
+
63
+ | Currency | 0.1.x | 0.2.0 |
64
+ | -------- | --------------- | -------------------------------------------- |
65
+ | TON | `:TON` | `:ton_mainnet` / `:ton_testnet` |
66
+ | Monero | `:monero` | `:standard` / `:integrated` / `:subaddress` |
67
+ | ETH | `nil` | `:eth` |
68
+ | SOL | `nil` | `:solana` |
69
+ | XLM | `nil` | `:account` / `:muxed` |
70
+
71
+ ```ruby
72
+ # 0.1.x
73
+ AdequateCryptoAddress::Ton.new(addr).send(:address_type) # private, => :TON
74
+
75
+ # 0.2.0
76
+ AdequateCryptoAddress.address_type(addr, :TON) # public, => :ton_mainnet
77
+ ```
78
+
79
+ Type symbols passed to `.valid?` for these currencies changed accordingly, e.g.
80
+ `valid?(addr, :TON, :ton_mainnet)`.
81
+
82
+ ### New network accessors
83
+
84
+ SegWit Bitcoin types are network-agnostic; call `.network` to enforce a network:
85
+
86
+ ```ruby
87
+ AdequateCryptoAddress.address('bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq', :btc).network #=> :mainnet
88
+ AdequateCryptoAddress.address('tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx', :btc).network #=> :testnet
89
+ ```
90
+
91
+ `Xmr#network` reports `:mainnet` / `:testnet` / `:stagenet`.
92
+
93
+ ### Internal rename (only if you subclassed `Bch`)
94
+
95
+ The BCH internal helper `address_type(address_code, address_type)` was renamed
96
+ to `type_mapping(address_code, address_type)`. The public `address_type` (no
97
+ arguments) now returns the detected type.
98
+
32
99
  ## Main API
33
100
 
34
- ##### .valid? (address, currency [, type = :prod])
101
+ ##### .valid? (address, currency [, type = nil])
35
102
 
36
103
  ###### Parameters
37
104
  * address - Wallet address to validate.
38
105
  * currency - Currency name string or symbol in any case, `:bitcoin` or `'BTC'` or `:btc` or `'BitCoin'`
39
106
  * type - Optional. You can enforce validation with specific type. Not all currencies support types.
40
107
 
41
- > Returns true if the address (string) is a valid wallet address for the crypto currency specified, see below for supported currencies.
108
+ > Returns true if the address (string) is a valid wallet address for the crypto currency specified, see below for supported currencies. An unknown currency raises `AdequateCryptoAddress::UnknownCurrency`; a `nil` or malformed address returns `false`.
109
+
110
+ ##### .address_type (address, currency)
111
+
112
+ > Returns the detected address type as a `Symbol` when the address is valid for the currency, or `nil` when it is not. The type vocabulary per currency is listed below.
113
+
114
+ ```ruby
115
+ AdequateCryptoAddress.address_type('bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq', :btc) #=> :segwit_v0_keyhash
116
+ AdequateCryptoAddress.address_type('not a real address', :btc) #=> nil
117
+ ```
42
118
 
43
119
  ### Supported crypto currencies
44
120
 
45
- * Bitcoin/BTC, `'bitcoin'` or `'BTC'` types: `:segwit_v0_keyhash :segwit_v0_scripthash :hash160 :p2sh`
121
+ * Bitcoin/BTC, `'bitcoin'` or `'BTC'` types: `:segwit_v0_keyhash :segwit_v0_scripthash :taproot :hash160 :p2sh :hash160test :p2shtest`. SegWit types are network-agnostic; call `AdequateCryptoAddress.address(addr, :btc).network` to get `:mainnet`/`:testnet`.
46
122
  * BitcoinCash/BCH, `'bitcoincash'` or `'BCH'` types: `:p2sh :p2pkh :p2pkhtest :p2shtest`
123
+ * Cardano/ADA, `'cardano'` or `'ADA'` types: `:prod :test`
47
124
  * Dash, `'dash'` or `'DASH'` types: `:prod :test`
125
+ * Dogecoin/DOGE, `'dogecoin'` or `'DOGE'` types: `:prod :test`
48
126
  * Zcash/ZEC, `'zcash'` or `'ZEC'` types: `:prod :test`
49
- * Ethereum/ETH, `'ethereum'` or `'ETH'`
50
- * Ripple/XRP, `'ripple'` or `'XRP'`
51
- * Toncoin, `'TON'`
52
- * Monero/XRM, `'monero'`
127
+ * Ethereum/ETH, `'ethereum'` or `'ETH'` type: `:eth`
128
+ * Litecoin/LTC, `'litecoin'` or `'LTC'` types: `:prod :test`
129
+ * Ripple/XRP, `'ripple'` or `'XRP'` type: `:common`
130
+ * Solana/SOL, `'solana'` or `'SOL'` type: `:solana`
131
+ * Stellar/XLM, `'stellar'` or `'XLM'` types: `:account :muxed`
132
+ * Toncoin/TON, `'TON'` or `'Toncoin'` types: `:ton_mainnet :ton_testnet`
133
+ * Monero/XMR, `'monero'` or `'XMR'` types: `:standard :integrated :subaddress` (`.network` reports `:mainnet`/`:testnet`/`:stagenet`)
134
+ * TRON/TRX, `'tron'` or `'TRX'` type: `:prod`
135
+ * Tezos/XTZ, `'tezos'` or `'XTZ'` types: `:implicit :originated`
136
+ * Cosmos/ATOM, `'cosmos'` or `'ATOM'` type: `:prod`
137
+ * BNB Smart Chain/BSC, `'bsc'` or `'binancesmartchain'` type: `:bsc` (Ethereum-format address)
138
+
139
+ ### Format coverage
140
+
141
+ This gem validates the mainstream, checksum-bearing **payment** address for each
142
+ supported chain. The following related formats are currently **out of scope** and
143
+ are rejected (they return `false`) rather than silently accepted. They may be
144
+ added in future releases:
145
+
146
+ * Bitcoin witness versions 2–16 and newer output forms (e.g. P2MR / BIP-360);
147
+ only v0 (Bech32) and v1 Taproot (Bech32m) are recognized.
148
+ * Litecoin MWEB (`ltcmweb1...`) addresses and future witness versions.
149
+ * Zcash Sapling, Unified, Orchard, and TEX addresses; only transparent
150
+ `t`-addresses are validated.
151
+ * XRP X-addresses (with embedded destination tags); only classic `r...`
152
+ addresses are validated.
153
+ * Dash Platform Bech32m addresses; only Core Base58Check addresses are validated.
154
+ * Cardano Byron and stake/reward addresses; only Shelley payment address
155
+ types 0–7 are validated. Stake addresses are intentionally excluded from a
156
+ payment-address validator.
157
+ * Stellar contract (`C...`) and other SEP-23 StrKey types; only `G` (account)
158
+ and `M` (muxed) addresses are validated.
159
+ * TON raw `workchain:account_id` addresses; only the Base64URL user-friendly
160
+ form is validated.
53
161
 
54
162
  ## Usage
55
163
 
@@ -110,7 +218,7 @@ class Model < ActiveRecord::Base
110
218
  end
111
219
 
112
220
  ```
113
- ### Add your currnecy
221
+ ### Add your currency
114
222
  ``` ruby
115
223
  # frozen_string_literal: true
116
224
  # for Rails /config/initializers/adequate_crypto_address.rb
@@ -133,34 +241,41 @@ AdequateCryptoAddress.valid?('addr', :coin) #=> true
133
241
 
134
242
  ## Development
135
243
 
136
- Run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
244
+ The repository pins Ruby with [mise](https://mise.jdx.dev/). Install the toolchain and dependencies, then run
245
+ the complete local quality gate:
246
+
247
+ ```bash
248
+ mise trust
249
+ mise install
250
+ mise run setup
251
+ mise run check
252
+ ```
253
+
254
+ Individual tasks are available as `mise run test`, `mise run lint`, and `mise run build`. Use
255
+ `mise run console` for an interactive prompt.
137
256
 
138
257
  ## Contributing
139
258
 
140
259
  1. Fork [the repo](https://github.com/vtm9/adequate_crypto_address)
141
- 2. Grab dependencies: `bundle install`
142
- 3. Make sure everything is working: `bundle exec rake spec`
260
+ 2. Trust and install the toolchain: `mise trust && mise install`
261
+ 3. Grab dependencies: `mise run setup`
143
262
  4. Make your changes
144
- 5. Test your changes
145
- 5. Create a Pull Request
146
- 6. Celebrate!!!!!
263
+ 5. Make sure everything is working: `mise run check`
264
+ 6. Create a pull request
147
265
 
148
266
  ## Notes
149
267
 
150
268
  Bug reports and pull requests are welcome on GitHub at https://github.com/vtm9/adequate_crypto_address/issues
151
269
 
152
- [gem-version-svg]: https://badge.fury.io/rb/adequate_crypto_address.svg
153
- [gem-version-link]: https://rubygems.org/gems/adequate_crypto_address
154
- [downloads-svg]: http://ruby-gem-downloads-badge.herokuapp.com/adequate_crypto_address
155
- [downloads-link]: https://rubygems.org/gems/adequate_crypto_address
156
- [build-status-svg]: https://travis-ci.org/vtm9/adequate_crypto_address.svg?branch=master
157
- [build-status-link]:https://travis-ci.org/vtm9/adequate_crypto_address
158
- [coverage-status-svg]: https://coveralls.io/repos/vtm9/adequate_crypto_address/badge.svg?branch=master
159
- [coverage-status-link]: https://coveralls.io/r/vtm9/adequate_crypto_address?branch=master
160
- [codeclimate-status-svg]: https://codeclimate.com/github/vtm9/adequate_crypto_address.svg
161
- [codeclimate-status-link]: https://codeclimate.com/github/vtm9/adequate_crypto_address
162
- [docs-rubydoc-svg]: https://img.shields.io/badge/docs-rubydoc-blue.svg
163
- [docs-rubydoc-link]: http://www.rubydoc.info/gems/adequate_crypto_address/
164
- [license-svg]: https://img.shields.io/badge/license-MIT-blue.svg
165
- [license-link]: https://github.com/vtm9/adequate_crypto_address/blob/master/LICENSE.txt
166
-
270
+ [gem-version-svg]: https://img.shields.io/gem/v/adequate_crypto_address.svg
271
+ [gem-version-link]: https://rubygems.org/gems/adequate_crypto_address
272
+ [downloads-svg]: https://img.shields.io/gem/dt/adequate_crypto_address.svg
273
+ [downloads-link]: https://rubygems.org/gems/adequate_crypto_address
274
+ [build-status-svg]: https://github.com/vtm9/adequate_crypto_address/actions/workflows/ci.yml/badge.svg?branch=master
275
+ [build-status-link]: https://github.com/vtm9/adequate_crypto_address/actions/workflows/ci.yml
276
+ [coverage-status-svg]: https://coveralls.io/repos/vtm9/adequate_crypto_address/badge.svg?branch=master
277
+ [coverage-status-link]: https://coveralls.io/r/vtm9/adequate_crypto_address?branch=master
278
+ [docs-rubydoc-svg]: https://img.shields.io/badge/docs-rubydoc-blue.svg
279
+ [docs-rubydoc-link]: http://www.rubydoc.info/gems/adequate_crypto_address/
280
+ [license-svg]: https://img.shields.io/badge/license-MIT-blue.svg
281
+ [license-link]: https://github.com/vtm9/adequate_crypto_address/blob/master/LICENSE.txt
data/bin/console CHANGED
@@ -5,7 +5,6 @@ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
5
5
  require 'adequate_crypto_address'
6
6
 
7
7
  ACA = AdequateCryptoAddress
8
- include AdequateCryptoAddress
9
8
 
10
9
  require 'pry'
11
10
  Pry.start
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AdequateCryptoAddress
4
+ class Ada
5
+ attr_reader :address, :type
6
+
7
+ BASE_ADDRESS_TYPES = [0, 1, 2, 3].freeze
8
+ POINTER_ADDRESS_TYPES = [4, 5].freeze
9
+ ENTERPRISE_ADDRESS_TYPES = [6, 7].freeze
10
+ VALID_ADDRESS_TYPES = (BASE_ADDRESS_TYPES + POINTER_ADDRESS_TYPES + ENTERPRISE_ADDRESS_TYPES).freeze
11
+ # Shelley payment addresses cap at ~103 chars (base); allow headroom while
12
+ # still bounding work, since Bech32 decoding runs with ignore_length: true.
13
+ MAX_LENGTH = 130
14
+
15
+ def initialize(address)
16
+ @address = address
17
+ @type = detect_type
18
+ end
19
+
20
+ def valid?(type = nil)
21
+ if type
22
+ address_type == type.to_sym
23
+ else
24
+ !address_type.nil?
25
+ end
26
+ end
27
+
28
+ # :prod or :test when valid, otherwise nil.
29
+ def address_type
30
+ type
31
+ end
32
+
33
+ private
34
+
35
+ def detect_type
36
+ decoded = safely_decode_address
37
+ return nil unless decoded
38
+
39
+ bytes, hrp = decoded
40
+
41
+ header = bytes.first
42
+ addr_type = header >> 4
43
+ network = header & 0x0f
44
+
45
+ return nil unless VALID_ADDRESS_TYPES.include?(addr_type)
46
+
47
+ return :prod if hrp == 'addr' && network == 1
48
+ return :test if hrp == 'addr_test' && network.zero?
49
+
50
+ nil
51
+ end
52
+
53
+ def safely_decode_address
54
+ return nil if address.to_s.length > MAX_LENGTH
55
+
56
+ decode_address
57
+ rescue StandardError
58
+ nil
59
+ end
60
+
61
+ def decode_address
62
+ actual_hrp, data, encoding = Utils::Bech32.decode(
63
+ address,
64
+ ignore_length: true,
65
+ include_encoding: true
66
+ )
67
+
68
+ return nil unless %w[addr addr_test].include?(actual_hrp)
69
+ return nil unless encoding == :bech32
70
+
71
+ bytes = Utils::Bech32.convert_bits(
72
+ data,
73
+ from_bits: 5,
74
+ to_bits: 8,
75
+ pad: false
76
+ )
77
+
78
+ return nil unless bytes
79
+ return nil if bytes.empty?
80
+
81
+ header = bytes.first
82
+ addr_type = header >> 4
83
+
84
+ return nil unless valid_length?(addr_type, bytes)
85
+
86
+ [bytes, actual_hrp]
87
+ end
88
+
89
+ def valid_length?(addr_type, bytes)
90
+ case addr_type
91
+ when *BASE_ADDRESS_TYPES
92
+ # header (1) + payment hash (28) + stake hash (28)
93
+ bytes.length == 57
94
+ when *POINTER_ADDRESS_TYPES
95
+ valid_pointer?(bytes.drop(29))
96
+ when *ENTERPRISE_ADDRESS_TYPES
97
+ # header (1) + payment hash (28)
98
+ bytes.length == 29
99
+ else
100
+ false
101
+ end
102
+ end
103
+
104
+ def valid_pointer?(pointer)
105
+ return false if pointer.length < 3
106
+
107
+ index = 0
108
+ 3.times do
109
+ first_byte = true
110
+ loop do
111
+ return false if index >= pointer.length
112
+
113
+ byte = pointer[index]
114
+ index += 1
115
+ return false if first_byte && byte == 0x80
116
+
117
+ first_byte = false
118
+ break if byte.nobits?(0x80)
119
+ end
120
+ end
121
+
122
+ index == pointer.length
123
+ end
124
+ end
125
+
126
+ Cardano = Ada
127
+ end
@@ -6,13 +6,14 @@ module AdequateCryptoAddress
6
6
  EXPECTED_LENGTH = 50
7
7
  ADDRESS_TYPES = {}.freeze
8
8
  ALPHABET_TYPE = :bitcoin
9
+ MAX_LENGTH = 100
9
10
 
10
11
  attr_reader :address, :type
11
12
  alias raw_address address
12
13
 
13
14
  def initialize(address)
14
15
  @address = address
15
- @type = address_type
16
+ @type = detect_type
16
17
  end
17
18
 
18
19
  def valid?(validated_type = nil)
@@ -23,26 +24,41 @@ module AdequateCryptoAddress
23
24
  end
24
25
  end
25
26
 
27
+ # Public contract: the detected network type Symbol, or nil when invalid.
28
+ def address_type
29
+ type
30
+ end
31
+
26
32
  private
27
33
 
28
34
  attr_reader :decoded
29
35
 
30
- def address_type
31
- @decoded = begin
32
- decode_base58
33
- rescue StandardError
34
- nil
35
- end
36
- if decoded && decoded.bytesize == self.class::EXPECTED_LENGTH && valid_address_checksum?
37
- self.class::ADDRESS_TYPES.each do |net_type, net_prefixes|
38
- net_prefixes.each do |net_prefix|
39
- return net_type if decoded.start_with?(net_prefix)
40
- end
41
- end
36
+ def too_long?
37
+ address.to_s.length > self.class::MAX_LENGTH
38
+ end
39
+
40
+ def detect_type
41
+ return nil if too_long?
42
+
43
+ @decoded = safely_decode_base58
44
+ return unless valid_decoded_address?
45
+
46
+ matching_type = self.class::ADDRESS_TYPES.find do |_network, prefixes|
47
+ prefixes.any? { |prefix| decoded.start_with?(prefix) }
42
48
  end
49
+ matching_type&.first
50
+ end
51
+
52
+ def safely_decode_base58
53
+ decode_base58
54
+ rescue StandardError
43
55
  nil
44
56
  end
45
57
 
58
+ def valid_decoded_address?
59
+ decoded&.bytesize == self.class::EXPECTED_LENGTH && valid_address_checksum?
60
+ end
61
+
46
62
  def decode_base58
47
63
  Base58.base58_to_binary(address, self.class::ALPHABET_TYPE).each_byte.map { |b| b.to_s(16).rjust(2, '0') }.join
48
64
  end
@@ -50,7 +66,7 @@ module AdequateCryptoAddress
50
66
  def valid_address_checksum?
51
67
  return false unless decoded
52
68
 
53
- checksum(decoded[0...-8]) == decoded[-8..-1]
69
+ checksum(decoded[0...-8]) == decoded[-8..]
54
70
  end
55
71
 
56
72
  def checksum(bytes)
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AdequateCryptoAddress
4
+ # Cosmos Hub (ATOM) account addresses are Bech32 (not Bech32m) with the
5
+ # `cosmos` human-readable part and a 20-byte address payload.
6
+ # https://docs.cosmos.network/main/build/spec/addresses/bech32
7
+ class Atom
8
+ HRP = 'cosmos'
9
+ PAYLOAD_BYTES = 20
10
+ MAX_LENGTH = 90
11
+
12
+ attr_reader :address, :type
13
+
14
+ def initialize(address)
15
+ @address = address
16
+ @type = detect_type
17
+ end
18
+
19
+ def valid?(validated_type = nil)
20
+ return !type.nil? unless validated_type
21
+
22
+ type == validated_type.to_sym
23
+ end
24
+
25
+ # :prod when the address is a valid cosmos account address, otherwise nil.
26
+ def address_type
27
+ type
28
+ end
29
+
30
+ private
31
+
32
+ def detect_type
33
+ return nil if address.to_s.length > MAX_LENGTH
34
+
35
+ :prod if valid_bech32?
36
+ end
37
+
38
+ def valid_bech32?
39
+ decoded = Utils::Bech32.decode(address, include_encoding: true)
40
+ return false unless decoded
41
+
42
+ hrp, data, encoding = decoded
43
+ return false unless hrp == HRP && encoding == :bech32
44
+
45
+ bytes = Utils::Bech32.convert_bits(data, from_bits: 5, to_bits: 8, pad: false)
46
+ bytes&.length == PAYLOAD_BYTES
47
+ rescue StandardError
48
+ false
49
+ end
50
+ end
51
+
52
+ Cosmos = Atom
53
+ end