adequate_crypto_address 0.1.11 → 0.2.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 +4 -4
- data/CHANGELOG.md +70 -0
- data/README.md +143 -29
- data/bin/console +0 -1
- data/lib/adequate_crypto_address/ada.rb +127 -0
- data/lib/adequate_crypto_address/altcoin.rb +30 -14
- data/lib/adequate_crypto_address/bch.rb +84 -30
- data/lib/adequate_crypto_address/btc.rb +86 -59
- data/lib/adequate_crypto_address/doge.rb +12 -0
- data/lib/adequate_crypto_address/eth.rb +8 -7
- data/lib/adequate_crypto_address/ltc.rb +45 -37
- data/lib/adequate_crypto_address/sol.rb +36 -0
- data/lib/adequate_crypto_address/ton.rb +54 -4
- data/lib/adequate_crypto_address/utils/bch.rb +8 -28
- data/lib/adequate_crypto_address/utils/bech32.rb +136 -107
- data/lib/adequate_crypto_address/utils/monero_base58.rb +60 -0
- data/lib/adequate_crypto_address/utils/xlm.rb +25 -0
- data/lib/adequate_crypto_address/version.rb +1 -1
- data/lib/adequate_crypto_address/xlm.rb +75 -0
- data/lib/adequate_crypto_address/xmr.rb +63 -15
- data/lib/adequate_crypto_address.rb +27 -4
- metadata +11 -43
- data/.coveralls.yml +0 -2
- data/.gitignore +0 -16
- data/.rspec +0 -3
- data/.rubocop.yml +0 -36
- data/.travis.yml +0 -13
- data/Gemfile +0 -12
- data/Makefile +0 -6
- data/Rakefile +0 -8
- data/adequate_crypto_address.gemspec +0 -25
- data/spec/adequate_crypto_address/bch_spec.rb +0 -77
- data/spec/adequate_crypto_address_spec.rb +0 -235
- data/spec/spec_helper.rb +0 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b1b6ee057501d6aee79d635204e6171784b6628e48663a74b0961ca837ebcf3c
|
|
4
|
+
data.tar.gz: c85732ee34eee9d27ac63ee4fe4a22079278011acda4fe1aeadfa215b8d5f85f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 535343adea99012947178355228daf705b9f01bd97b550ee8e01e0d3e71077df9f3b1b7aab26f249b8972ea1c4ccf82b276ef1c5ca054bdd398126f4aad8f5de
|
|
7
|
+
data.tar.gz: 4602af0c018876b2d3ad041253fd97de6c8d3c7709097ca68ddf5a95c09d983cd1623d6c38aa272fb07fd7503cf04ed5f8b4bfa99fecde28e55bd7b19e3c8781
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
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.2.0] - 2026-09-02
|
|
9
|
+
|
|
10
|
+
This release hardens the validators against malformed input found in the
|
|
11
|
+
September 2026 security review. Several addresses that previous versions
|
|
12
|
+
incorrectly accepted are now rejected, and the public `address_type` API is
|
|
13
|
+
consistent across every currency. See the "Upgrading from 0.1.x to 0.2.0"
|
|
14
|
+
section of the README for migration guidance.
|
|
15
|
+
|
|
16
|
+
### Security
|
|
17
|
+
|
|
18
|
+
- **BCH legacy Base58Check** now verifies the four-byte double-SHA256 checksum
|
|
19
|
+
and requires the exact 25-byte decoded length. One-character checksum
|
|
20
|
+
mutations are rejected.
|
|
21
|
+
- **BCH CashAddr** now accepts only the `bitcoincash`, `bchtest`, and `bchreg`
|
|
22
|
+
prefixes (an `evil:` prefix with a valid checksum is rejected) and validates
|
|
23
|
+
the reserved bit, type bits, declared hash size, and decoded payload length
|
|
24
|
+
(a shortened payload with a recomputed checksum is rejected).
|
|
25
|
+
- **TON** addresses are fully decoded: Base64URL payload, tag, workchain,
|
|
26
|
+
32-byte account ID, CRC16-CCITT checksum, and testnet flag. Arbitrary
|
|
27
|
+
48-character strings such as `"A" * 48` are no longer accepted.
|
|
28
|
+
- **Monero** is validated by decoding the Monero Base58 payload and checking the
|
|
29
|
+
network byte, structural length, and Keccak-256 checksum for standard,
|
|
30
|
+
integrated, and subaddress forms. Non-Base58 input such as `"4" + "!" * 94`
|
|
31
|
+
is rejected.
|
|
32
|
+
- **Bounded input**: all validators enforce a practical length cap before
|
|
33
|
+
Base58/Bech32 decoding, preventing pathological slowdowns on oversized input.
|
|
34
|
+
- Currency resolution now uses an explicit allowlist, so a `NoMethodError` from
|
|
35
|
+
malformed input can no longer be misreported as `UnknownCurrency`.
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- Public `AdequateCryptoAddress.address_type(address, currency)` with a
|
|
40
|
+
consistent contract: it returns the detected type as a `Symbol` when the
|
|
41
|
+
address is valid, or `nil` when it is not, for every supported currency.
|
|
42
|
+
- `Btc#network` and `Xmr#network` expose `:mainnet`/`:testnet`
|
|
43
|
+
(`:stagenet` for Monero) so callers can enforce a network.
|
|
44
|
+
- `Toncoin` alias for the `Ton` validator.
|
|
45
|
+
- `bundler-audit` dependency advisory check, wired into `mise run check` and CI.
|
|
46
|
+
- Regression specs for checksum mutations, oversized input, nil input, invalid
|
|
47
|
+
currency names, and the `address_type` contract across all currencies.
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
|
|
51
|
+
- **Breaking:** `address_type` is now public on every validator and returns a
|
|
52
|
+
type `Symbol` or `nil`. Previously it was private on most validators, returned
|
|
53
|
+
`nil` for ETH/SOL/XLM, and required arguments on BCH.
|
|
54
|
+
- **Breaking:** TON's detected type changed from `:TON` to `:ton_mainnet` /
|
|
55
|
+
`:ton_testnet`.
|
|
56
|
+
- **Breaking:** Monero's detected type is now `:standard`, `:integrated`, or
|
|
57
|
+
`:subaddress` (was `:monero`).
|
|
58
|
+
- ETH now reports `address_type` `:eth`, SOL reports `:solana`, and XLM reports
|
|
59
|
+
`:account` / `:muxed`.
|
|
60
|
+
- The BCH internal type-mapping helper was renamed from `address_type(code, type)`
|
|
61
|
+
to `type_mapping(code, type)`.
|
|
62
|
+
|
|
63
|
+
### Documentation
|
|
64
|
+
|
|
65
|
+
- Documented supported per-currency types, the `address_type` API, network
|
|
66
|
+
accessors, and an explicit "Format coverage" section listing address formats
|
|
67
|
+
that are intentionally out of scope (and rejected rather than silently
|
|
68
|
+
accepted).
|
|
69
|
+
|
|
70
|
+
[0.2.0]: https://github.com/vtm9/adequate_crypto_address/releases/tag/v0.2.0
|
data/README.md
CHANGED
|
@@ -29,27 +29,132 @@ Or install it yourself as:
|
|
|
29
29
|
gem install adequate_crypto_address
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
## Upgrading from 0.1.x to 0.2.0
|
|
33
|
+
|
|
34
|
+
0.2.0 hardens the validators against malformed input and makes the
|
|
35
|
+
`address_type` API consistent. Most callers of `.valid?` need no changes, but a
|
|
36
|
+
few behaviors changed. See the [CHANGELOG](CHANGELOG.md) for the full list.
|
|
37
|
+
|
|
38
|
+
### Stricter validation (addresses that used to pass may now fail)
|
|
39
|
+
|
|
40
|
+
Some malformed addresses that earlier versions incorrectly accepted are now
|
|
41
|
+
rejected. If your app stored or allow-listed such values, re-validate them:
|
|
42
|
+
|
|
43
|
+
- **BCH** legacy addresses with a bad checksum/length, CashAddr addresses with a
|
|
44
|
+
non-`bitcoincash`/`bchtest`/`bchreg` prefix (e.g. `evil:...`), or a payload
|
|
45
|
+
shorter than the version byte declares.
|
|
46
|
+
- **TON** any 48-character string (e.g. `"A" * 48`) — a valid tag, workchain,
|
|
47
|
+
and CRC16 checksum are now required.
|
|
48
|
+
- **Monero** anything that is not a checksum-valid Base58 address (e.g.
|
|
49
|
+
`"4" + "!" * 94`).
|
|
50
|
+
|
|
51
|
+
If you rely on a currency name, note that unknown names still raise
|
|
52
|
+
`AdequateCryptoAddress::UnknownCurrency`, but a `nil` or malformed **address**
|
|
53
|
+
now consistently returns `false` from `.valid?` instead of raising.
|
|
54
|
+
|
|
55
|
+
### `address_type` is now public and consistent
|
|
56
|
+
|
|
57
|
+
`AdequateCryptoAddress.address_type(address, currency)` is a supported public
|
|
58
|
+
method for every currency. It returns the detected type as a `Symbol` when the
|
|
59
|
+
address is valid, or `nil` when it is not.
|
|
60
|
+
|
|
61
|
+
Some returned type symbols changed. Update any code that compared against the
|
|
62
|
+
old values:
|
|
63
|
+
|
|
64
|
+
| Currency | 0.1.x | 0.2.0 |
|
|
65
|
+
| -------- | --------------- | -------------------------------------------- |
|
|
66
|
+
| TON | `:TON` | `:ton_mainnet` / `:ton_testnet` |
|
|
67
|
+
| Monero | `:monero` | `:standard` / `:integrated` / `:subaddress` |
|
|
68
|
+
| ETH | `nil` | `:eth` |
|
|
69
|
+
| SOL | `nil` | `:solana` |
|
|
70
|
+
| XLM | `nil` | `:account` / `:muxed` |
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
# 0.1.x
|
|
74
|
+
AdequateCryptoAddress::Ton.new(addr).send(:address_type) # private, => :TON
|
|
75
|
+
|
|
76
|
+
# 0.2.0
|
|
77
|
+
AdequateCryptoAddress.address_type(addr, :TON) # public, => :ton_mainnet
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Type symbols passed to `.valid?` for these currencies changed accordingly, e.g.
|
|
81
|
+
`valid?(addr, :TON, :ton_mainnet)`.
|
|
82
|
+
|
|
83
|
+
### New network accessors
|
|
84
|
+
|
|
85
|
+
SegWit Bitcoin types are network-agnostic; call `.network` to enforce a network:
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
AdequateCryptoAddress.address('bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq', :btc).network #=> :mainnet
|
|
89
|
+
AdequateCryptoAddress.address('tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx', :btc).network #=> :testnet
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`Xmr#network` reports `:mainnet` / `:testnet` / `:stagenet`.
|
|
93
|
+
|
|
94
|
+
### Internal rename (only if you subclassed `Bch`)
|
|
95
|
+
|
|
96
|
+
The BCH internal helper `address_type(address_code, address_type)` was renamed
|
|
97
|
+
to `type_mapping(address_code, address_type)`. The public `address_type` (no
|
|
98
|
+
arguments) now returns the detected type.
|
|
99
|
+
|
|
32
100
|
## Main API
|
|
33
101
|
|
|
34
|
-
##### .valid? (address, currency [, type =
|
|
102
|
+
##### .valid? (address, currency [, type = nil])
|
|
35
103
|
|
|
36
104
|
###### Parameters
|
|
37
105
|
* address - Wallet address to validate.
|
|
38
106
|
* currency - Currency name string or symbol in any case, `:bitcoin` or `'BTC'` or `:btc` or `'BitCoin'`
|
|
39
107
|
* type - Optional. You can enforce validation with specific type. Not all currencies support types.
|
|
40
108
|
|
|
41
|
-
> Returns true if the address (string) is a valid wallet address for the crypto currency specified, see below for supported currencies.
|
|
109
|
+
> 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`.
|
|
110
|
+
|
|
111
|
+
##### .address_type (address, currency)
|
|
112
|
+
|
|
113
|
+
> 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.
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
AdequateCryptoAddress.address_type('bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq', :btc) #=> :segwit_v0_keyhash
|
|
117
|
+
AdequateCryptoAddress.address_type('not a real address', :btc) #=> nil
|
|
118
|
+
```
|
|
42
119
|
|
|
43
120
|
### Supported crypto currencies
|
|
44
121
|
|
|
45
|
-
* Bitcoin/BTC, `'bitcoin'` or `'BTC'` types: `:segwit_v0_keyhash :segwit_v0_scripthash :hash160 :p2sh`
|
|
122
|
+
* 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
123
|
* BitcoinCash/BCH, `'bitcoincash'` or `'BCH'` types: `:p2sh :p2pkh :p2pkhtest :p2shtest`
|
|
124
|
+
* Cardano/ADA, `'cardano'` or `'ADA'` types: `:prod :test`
|
|
47
125
|
* Dash, `'dash'` or `'DASH'` types: `:prod :test`
|
|
126
|
+
* Dogecoin/DOGE, `'dogecoin'` or `'DOGE'` types: `:prod :test`
|
|
48
127
|
* Zcash/ZEC, `'zcash'` or `'ZEC'` types: `:prod :test`
|
|
49
|
-
* Ethereum/ETH, `'ethereum'` or `'ETH'`
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
128
|
+
* Ethereum/ETH, `'ethereum'` or `'ETH'` type: `:eth`
|
|
129
|
+
* Litecoin/LTC, `'litecoin'` or `'LTC'` types: `:prod :test`
|
|
130
|
+
* Ripple/XRP, `'ripple'` or `'XRP'` type: `:common`
|
|
131
|
+
* Solana/SOL, `'solana'` or `'SOL'` type: `:solana`
|
|
132
|
+
* Stellar/XLM, `'stellar'` or `'XLM'` types: `:account :muxed`
|
|
133
|
+
* Toncoin/TON, `'TON'` or `'Toncoin'` types: `:ton_mainnet :ton_testnet`
|
|
134
|
+
* Monero/XMR, `'monero'` or `'XMR'` types: `:standard :integrated :subaddress` (`.network` reports `:mainnet`/`:testnet`/`:stagenet`)
|
|
135
|
+
|
|
136
|
+
### Format coverage
|
|
137
|
+
|
|
138
|
+
This gem validates the mainstream, checksum-bearing **payment** address for each
|
|
139
|
+
supported chain. The following related formats are currently **out of scope** and
|
|
140
|
+
are rejected (they return `false`) rather than silently accepted. They may be
|
|
141
|
+
added in future releases:
|
|
142
|
+
|
|
143
|
+
* Bitcoin witness versions 2–16 and newer output forms (e.g. P2MR / BIP-360);
|
|
144
|
+
only v0 (Bech32) and v1 Taproot (Bech32m) are recognized.
|
|
145
|
+
* Litecoin MWEB (`ltcmweb1...`) addresses and future witness versions.
|
|
146
|
+
* Zcash Sapling, Unified, Orchard, and TEX addresses; only transparent
|
|
147
|
+
`t`-addresses are validated.
|
|
148
|
+
* XRP X-addresses (with embedded destination tags); only classic `r...`
|
|
149
|
+
addresses are validated.
|
|
150
|
+
* Dash Platform Bech32m addresses; only Core Base58Check addresses are validated.
|
|
151
|
+
* Cardano Byron and stake/reward addresses; only Shelley payment address
|
|
152
|
+
types 0–7 are validated. Stake addresses are intentionally excluded from a
|
|
153
|
+
payment-address validator.
|
|
154
|
+
* Stellar contract (`C...`) and other SEP-23 StrKey types; only `G` (account)
|
|
155
|
+
and `M` (muxed) addresses are validated.
|
|
156
|
+
* TON raw `workchain:account_id` addresses; only the Base64URL user-friendly
|
|
157
|
+
form is validated.
|
|
53
158
|
|
|
54
159
|
## Usage
|
|
55
160
|
|
|
@@ -110,7 +215,7 @@ class Model < ActiveRecord::Base
|
|
|
110
215
|
end
|
|
111
216
|
|
|
112
217
|
```
|
|
113
|
-
### Add your
|
|
218
|
+
### Add your currency
|
|
114
219
|
``` ruby
|
|
115
220
|
# frozen_string_literal: true
|
|
116
221
|
# for Rails /config/initializers/adequate_crypto_address.rb
|
|
@@ -133,34 +238,43 @@ AdequateCryptoAddress.valid?('addr', :coin) #=> true
|
|
|
133
238
|
|
|
134
239
|
## Development
|
|
135
240
|
|
|
136
|
-
|
|
241
|
+
The repository pins Ruby with [mise](https://mise.jdx.dev/). Install the toolchain and dependencies, then run
|
|
242
|
+
the complete local quality gate:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
mise trust
|
|
246
|
+
mise install
|
|
247
|
+
mise run setup
|
|
248
|
+
mise run check
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Individual tasks are available as `mise run test`, `mise run lint`, and `mise run build`. Use
|
|
252
|
+
`mise run console` for an interactive prompt.
|
|
137
253
|
|
|
138
254
|
## Contributing
|
|
139
255
|
|
|
140
256
|
1. Fork [the repo](https://github.com/vtm9/adequate_crypto_address)
|
|
141
|
-
2.
|
|
142
|
-
3.
|
|
257
|
+
2. Trust and install the toolchain: `mise trust && mise install`
|
|
258
|
+
3. Grab dependencies: `mise run setup`
|
|
143
259
|
4. Make your changes
|
|
144
|
-
5.
|
|
145
|
-
|
|
146
|
-
6. Celebrate!!!!!
|
|
260
|
+
5. Make sure everything is working: `mise run check`
|
|
261
|
+
6. Create a pull request
|
|
147
262
|
|
|
148
263
|
## Notes
|
|
149
264
|
|
|
150
265
|
Bug reports and pull requests are welcome on GitHub at https://github.com/vtm9/adequate_crypto_address/issues
|
|
151
266
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
267
|
+
[gem-version-svg]: https://badge.fury.io/rb/adequate_crypto_address.svg
|
|
268
|
+
[gem-version-link]: https://rubygems.org/gems/adequate_crypto_address
|
|
269
|
+
[downloads-svg]: http://ruby-gem-downloads-badge.herokuapp.com/adequate_crypto_address
|
|
270
|
+
[downloads-link]: https://rubygems.org/gems/adequate_crypto_address
|
|
271
|
+
[build-status-svg]: https://github.com/vtm9/adequate_crypto_address/actions/workflows/ci.yml/badge.svg?branch=master
|
|
272
|
+
[build-status-link]: https://github.com/vtm9/adequate_crypto_address/actions/workflows/ci.yml
|
|
273
|
+
[coverage-status-svg]: https://coveralls.io/repos/vtm9/adequate_crypto_address/badge.svg?branch=master
|
|
274
|
+
[coverage-status-link]: https://coveralls.io/r/vtm9/adequate_crypto_address?branch=master
|
|
275
|
+
[codeclimate-status-svg]: https://codeclimate.com/github/vtm9/adequate_crypto_address.svg
|
|
276
|
+
[codeclimate-status-link]: https://codeclimate.com/github/vtm9/adequate_crypto_address
|
|
277
|
+
[docs-rubydoc-svg]: https://img.shields.io/badge/docs-rubydoc-blue.svg
|
|
278
|
+
[docs-rubydoc-link]: http://www.rubydoc.info/gems/adequate_crypto_address/
|
|
279
|
+
[license-svg]: https://img.shields.io/badge/license-MIT-blue.svg
|
|
280
|
+
[license-link]: https://github.com/vtm9/adequate_crypto_address/blob/master/LICENSE.txt
|
data/bin/console
CHANGED
|
@@ -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 =
|
|
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
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
69
|
+
checksum(decoded[0...-8]) == decoded[-8..]
|
|
54
70
|
end
|
|
55
71
|
|
|
56
72
|
def checksum(bytes)
|