exocrypto 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 675e5aafa265e33dec84cb3427d8c71afcd3fec9ab3118210be650e96c5b3101
4
+ data.tar.gz: eff4eeb5a746b56925a0860e6c867ced98f4bb480477c48f14cabdb777487297
5
+ SHA512:
6
+ metadata.gz: 20e268da580a84adfcc9563fa2004037b41e05ef288523925d4c5fc070aa9d0f454dbcda19ae8b65a0e5abab82d144faa4e3e1dee5a5f83e25cf838f236ddb6a
7
+ data.tar.gz: bfff000fe68bbb3affd5360fdcc94e63b4d714812cc0402dc0c05ecab307d2782e8ec9694e5a028f851788d5db4d0b2882e0155abcaf0275935282829da224a5
data/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # ExoCrypto
2
+
3
+ CryptoHelpers: This gem provides helpers for crypto calculus.
4
+
5
+ - Addresses
6
+ - HD Wallets
7
+
8
+ [reference for gem creation: [newgem-template](https://github.com/wycats/newgem-template)]
9
+
10
+ [reference for git init: [github init](https://gist.github.com/seankross/8830032)]
11
+
12
+
13
+ ## Getting started
14
+
15
+ `bundle`
16
+
17
+ `rake build`
18
+
19
+ `gem push [GEM.gem]`
20
+
21
+
22
+ ### Requirements
23
+
24
+ - Ruby 2.5+
25
+ - Bitcoin-ruby 0.0.20
26
+ - BIP44 0.2.18
27
+ - ExoBasic 0.1.0
@@ -0,0 +1,317 @@
1
+ require 'set'
2
+ require 'openssl'
3
+ require 'ecdsa'
4
+ require 'digest'
5
+ require 'digest/sha3'
6
+ require 'rlp'
7
+ require 'money-tree'
8
+ require 'bip_mnemonic'
9
+ require 'eth'
10
+ require "bip44/version"
11
+ require 'bip44/utils'
12
+ require 'bip44/bitcoin'
13
+ require 'bip44/ethereum'
14
+ require 'bip44/wallet'
15
+
16
+ module ExoCrypto
17
+ # see: https://github.com/wuminzhe/bip44
18
+ class Bip44WalletProvider
19
+ TESTNET_TICKER = 'TESTNET'
20
+
21
+ # see: https://github.com/satoshilabs/slips/blob/master/slip-0044.md
22
+ def self.ticker_map
23
+ {
24
+ 'TESTNET' => {
25
+ :slip => 1,
26
+ :precision => 8
27
+ },
28
+ 'BTC' => {
29
+ :slip => 0,
30
+ :precision => 8
31
+ },
32
+ 'ETH' => {
33
+ :slip => 60,
34
+ :precision => 18
35
+ },
36
+ 'DASH' => {
37
+ :slip => 5,
38
+ :precision => 8
39
+ },
40
+ 'DOGE' => {
41
+ :slip => 3,
42
+ :precision => 8
43
+ },
44
+ 'LTC' => {
45
+ :slip => 2,
46
+ :precision => 8
47
+ }
48
+ }
49
+ end
50
+
51
+ def self.coins
52
+ Bip44WalletProvider.ticker_map.keys.to_set.delete(Bip44WalletProvider::TESTNET_TICKER)
53
+ end
54
+
55
+ def self.precision_of(ticker)
56
+ Bip44WalletProvider.ticker_map[ticker][:precision]
57
+ end
58
+
59
+ # see: https://github.com/citizen010/bitcoin-prefixes-address-list
60
+ def self.address_details(sub_wallet, is_testnet, include_private)
61
+ if include_private
62
+ {
63
+ :bitcoin_address => sub_wallet.bitcoin_address(testnet: is_testnet),
64
+ :ethereum_address => sub_wallet.ethereum_address,
65
+ :public_hex => sub_wallet.public_key,
66
+ :private_hex => sub_wallet.private_key,
67
+ :private_wif => sub_wallet.wif(testnet: is_testnet)
68
+ }
69
+ else
70
+ {
71
+ :bitcoin_address => sub_wallet.bitcoin_address(testnet: is_testnet),
72
+ :ethereum_address => sub_wallet.ethereum_address,
73
+ :public_hex => sub_wallet.public_key
74
+ }
75
+ end
76
+ end
77
+
78
+ def self.master_details(mnemonic, wallet, is_testnet)
79
+ {
80
+ :mnemonic => mnemonic,
81
+ :seed_hex => BipMnemonic.to_seed(mnemonic: mnemonic),
82
+ :xpub => wallet.xpub(testnet: is_testnet),
83
+ :xprv => wallet.xprv(testnet: is_testnet),
84
+ :details => Bip44WalletProvider.address_details(wallet, is_testnet, true)
85
+ }
86
+ end
87
+
88
+ def self.create_wallet(coin_ticker='BTC')
89
+ words = BipMnemonic.to_mnemonic(bits: 128)
90
+ seed = BipMnemonic.to_seed(mnemonic: words)
91
+
92
+ coin = ticker_map[coin_ticker][:slip]
93
+ path = "m/44'/#{coin}'/0'"
94
+ wallet = Bip44::Wallet.from_seed(seed, path)
95
+ testnet = coin_ticker == Bip44WalletProvider::TESTNET_TICKER
96
+
97
+ {
98
+ :obj => wallet,
99
+ :seed_hex => seed,
100
+ :path => path,
101
+ :details => Bip44WalletProvider.master_details(words, wallet, testnet)
102
+ }
103
+ end
104
+
105
+ def self.create_subwallet(xpub, sub_path, is_testnet=false)
106
+ wallet = Bip44::Wallet.from_xpub(xpub)
107
+ sub_wallet = wallet.sub_wallet(sub_path)
108
+
109
+ {
110
+ :obj => sub_wallet,
111
+ :path => sub_path,
112
+ :details => Bip44WalletProvider.address_details(sub_wallet, is_testnet, false)
113
+ }
114
+ end
115
+
116
+ def self.seed_of(mnemonic, password=nil)
117
+ #BipMnemonic.to_seed(mnemonic: mnemonic)
118
+ OpenSSL::PKCS5.pbkdf2_hmac(mnemonic,
119
+ "mnemonic#{password}",
120
+ 2048,
121
+ 64,
122
+ OpenSSL::Digest::SHA512.new)
123
+ .unpack('H*').first
124
+ end
125
+
126
+ def self.btc_address_details_of(seed, path, is_testnet=false)
127
+ wallet = Bip44::Wallet.from_seed(seed, path)
128
+ details = Bip44WalletProvider.address_details(wallet, is_testnet, true)
129
+ details[:address] = details[:bitcoin_address]
130
+ details.delete(:bitcoin_address)
131
+ details.delete(:ethereum_address)
132
+ details
133
+ end
134
+
135
+ def self.from_absolute_path(path)
136
+ elements = path.split('/')
137
+ derivation = "m/#{elements.take(4).drop(1).join('/')}"
138
+ relative = "m/#{elements.drop(4).join('/')}"
139
+
140
+ {
141
+ :absolute_path => path,
142
+ :derivation => derivation,
143
+ :relative_path => relative
144
+ }
145
+ end
146
+
147
+ #
148
+ # see: https://gobittest.appspot.com/PrivateKey
149
+ # https://github.com/dougal/base58/blob/master/lib/base58.rb
150
+ # http://royalforkblog.github.io/2014/07/31/address-gen
151
+ #
152
+ def self.sha256(hex)
153
+ Digest::SHA256.hexdigest([hex].pack('H*'))
154
+ end
155
+
156
+ def self.ripemd160(hex)
157
+ Digest::RMD160.hexdigest([hex].pack('H*'))
158
+ end
159
+
160
+ def self.checksum(hex)
161
+ Bip44WalletProvider.sha256(Bip44WalletProvider.sha256(hex))[0...8]
162
+ end
163
+
164
+ def self.int_to_hex(int)
165
+ hex = int.to_s(16)
166
+ #
167
+ # The hex string must always consist of an even number of characters,
168
+ # otherwise the pack() parsing will be misaligned.
169
+ #
170
+ (hex.length % 2 == 0) ? hex : ('0' + hex)
171
+ end
172
+
173
+ def self.btc_int_to_base58(int_val, leading_zero_bytes=0)
174
+ alpha = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
175
+ base58_val, base = '', alpha.size
176
+ while int_val > 0
177
+ int_val, remainder = int_val.divmod(base)
178
+ base58_val = alpha[remainder] + base58_val
179
+ end
180
+
181
+ base58_val
182
+ end
183
+
184
+ def self.btc_encode_base58(hex)
185
+ leading_zero_bytes = (hex.match(/^([0]+)/) ? $1 : '').size / 2
186
+ ('1' * leading_zero_bytes) + Bip44WalletProvider.btc_int_to_base58(hex.to_i(16))
187
+ end
188
+
189
+ def self.btc_base58_to_int(base58_val)
190
+ alpha = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
191
+ int_val, base = 0, alpha.size
192
+ base58_val.reverse.split(//).each_with_index do |char, index|
193
+ char_index = alpha.index(char)
194
+ int_val += char_index * base**index
195
+ end
196
+
197
+ int_val
198
+ end
199
+
200
+ def self.btc_decode_base58(base58_val)
201
+ nzeroes = base58_val.chars.find_index { |c| c != '1' } || base58_val.length - 1
202
+ prefix = nzeroes < 0 ? '' : '00' * nzeroes
203
+ decoded = Bip44WalletProvider.int_to_hex(Bip44WalletProvider.btc_base58_to_int(base58_val))
204
+
205
+ [prefix + decoded].pack('H*')
206
+ end
207
+
208
+ def self.btc_wif_of_privkey(hex, is_compressed=false, is_testnet=false)
209
+ priv_key_version = is_testnet ? 'ef' : '80'
210
+ data = priv_key_version + hex
211
+ if is_compressed
212
+ data += '01'
213
+ end
214
+
215
+ Bip44WalletProvider.btc_encode_base58(data + Bip44WalletProvider.checksum(data))
216
+ end
217
+
218
+ def self.btc_privkey_of_wif(wif, is_compressed=false)
219
+ result = Bip44WalletProvider.btc_decode_base58(wif)[1..-5].unpack('H*').first
220
+ if is_compressed
221
+ result = result[0...-2]
222
+ end
223
+ result
224
+ end
225
+
226
+ def self.btc_pubkey_of_wif(wif, is_compressed=false, is_testnet=false)
227
+ priv_key = Bip44WalletProvider.btc_privkey_of_wif(wif, is_compressed)
228
+ group = OpenSSL::PKey::EC::Group.new('secp256k1')
229
+ public_key = group.generator.mul(OpenSSL::BN.new(priv_key, 16))
230
+ decompressed_public_key_hex = public_key.to_bn.to_s(16)
231
+
232
+ # NOTE: for compression
233
+ public_key_hex = decompressed_public_key_hex
234
+ if is_compressed
235
+ x = decompressed_public_key_hex[2..-1][0..63]
236
+ y = decompressed_public_key_hex[2..-1][64..-1]
237
+ leader = y.to_i(16) % 2 == 0 ? '02' : '03'
238
+ public_key_hex = leader + x
239
+ end
240
+
241
+ public_key_hash = Bip44WalletProvider.ripemd160(Bip44WalletProvider.sha256(public_key_hex))
242
+ network = is_testnet ? '6f' : '00'
243
+ data = network + public_key_hash
244
+
245
+ public_address =
246
+ Bip44WalletProvider.btc_encode_base58(data + Bip44WalletProvider.checksum(data))
247
+
248
+ {
249
+ :decompressed_public_key => decompressed_public_key_hex,
250
+ :public_key => public_key_hex,
251
+ :bitcoin_address => public_address
252
+ }
253
+ end
254
+ def self.btc_pubkey_of_wif_ecdsa(wif, is_compressed=false, is_testnet=false)
255
+ priv_key = Bip44WalletProvider.btc_privkey_of_wif(wif, is_compressed)
256
+
257
+ # kpub = kpriv * G
258
+ curve = ECDSA::Group::Secp256k1
259
+ pub_key_point = curve.generator.multiply_by_scalar(priv_key.to_i(16))
260
+
261
+ #
262
+ # uncompressed, pub key is in form 0x04 + x + y
263
+ # compressed, pub key is in form
264
+ # 0x02 + x if y is even
265
+ # 0x03 + x if y is odd
266
+ # Getting encodings right is difficult...pub.x is a Bignum. Bignum + Bignum is
267
+ # a biggernum, which isn't really what we want. We want string concatenation. To do that,
268
+ # we translate to hex, concatenate the hex, and pack it back into a string to
269
+ # concatenate with our leading byte
270
+ #
271
+ # pub.x is a Bignum,
272
+ # so we must concatenate our compression byte with the hex representation of pub_key.x
273
+ #
274
+ decompressed_pub_key = "\x04" +
275
+ [pub_key_point.x.to_s(16)].pack('H*') +
276
+ [pub_key_point.y.to_s(16)].pack('H*')
277
+ pub_key = decompressed_pub_key
278
+ if is_compressed
279
+ leader = pub_key_point.y % 2 == 0 ? "\x02" : "\x03"
280
+ pub_key = leader + [pub_key_point.x.to_s(16)].pack('H*')
281
+ end
282
+
283
+ # ripe160(sha256(pub_key))
284
+ pub_key_hash = Digest::RMD160.digest(Digest::SHA256.digest(pub_key))
285
+
286
+ #
287
+ # prepend version to our double hashed pub key, append checksum
288
+ # Bitcoin: 0x00
289
+ # Testnet: 0x6f
290
+ #
291
+ network = is_testnet ? "\x6f" : "\x00"
292
+ pub_key_hash_and_version = network + pub_key_hash
293
+
294
+ # add checksum
295
+ pub_key_hash_and_version_str = pub_key_hash_and_version.unpack('H*').first
296
+ pub_key_hash_and_version_and_checksum = pub_key_hash_and_version_str +
297
+ Bip44WalletProvider.checksum(
298
+ pub_key_hash_and_version_str)
299
+
300
+ # base58 encode version, hash, checksum
301
+ pub_addr = Bip44WalletProvider.btc_encode_base58(pub_key_hash_and_version_and_checksum)
302
+
303
+ {
304
+ :decompressed_public_key => decompressed_pub_key.unpack("H*").first,
305
+ :public_key => pub_key.unpack("H*").first,
306
+ :bitcoin_address => pub_addr
307
+ }
308
+ end
309
+
310
+ def self.btc_pubkey_of_privkey(hex, is_compressed=false, is_testnet=false)
311
+ priv = Bip44WalletProvider.btc_wif_of_privkey(hex, is_compressed, is_testnet)
312
+
313
+ Bip44WalletProvider.btc_pubkey_of_wif(priv, is_compressed, is_testnet)
314
+ end
315
+
316
+ end
317
+ end
@@ -0,0 +1,97 @@
1
+ module ExoCrypto
2
+ class CryptoCalc
3
+ def self.mul(a, b)
4
+ BigDecimal(a) * BigDecimal(b)
5
+ end
6
+
7
+ def self.s_to_satoshis(amount, precision)
8
+ (BigDecimal(amount) * BigDecimal(10 ** precision)).to_i
9
+ end
10
+
11
+ def self.s_from_satoshis(amount, precision)
12
+ (amount / BigDecimal(10 ** precision)).to_s
13
+ end
14
+
15
+ def self.fixed_length_to_s(amount, precision)
16
+ "%.#{precision}f" % amount
17
+ end
18
+
19
+ def self.to_fixed_length(amount, currency)
20
+ precision = Bip44WalletProvider.precision_of(currency)
21
+ CryptoCalc.fixed_length_to_s(CryptoCalc.s_from_satoshis(amount, precision), precision)
22
+ end
23
+
24
+ # see: https://reinteractive.com/posts/373-diving-into-the-ruby-source-code-bigdecimal-rounding-options
25
+ def self.get_satoshis(currency, exchange_rate, remaining)
26
+ precision = Bip44WalletProvider.precision_of(currency)
27
+ amount_to_be_paid = 0
28
+ rate = BigDecimal(exchange_rate)
29
+ if !rate.zero?
30
+ amount_to_be_paid = BigDecimal(remaining) / rate
31
+ amount_to_be_paid = (amount_to_be_paid.round(precision) * BigDecimal(10 ** precision)).to_i
32
+ end
33
+
34
+ amount_to_be_paid
35
+ end
36
+
37
+ def self.do_partial_payments(request, payments)
38
+ epoch = ExoBasic::Timer::EPOCH.to_datetime
39
+ xs = payments.map do |payment|
40
+ if payment[:paid]
41
+ precision = Bip44WalletProvider.precision_of(payment[:currency])
42
+ satoshis = BigDecimal(payment[:amount_paid]) * BigDecimal(payment[:exchange_rate])
43
+
44
+ [satoshis / BigDecimal(10 ** precision), payment[:date]]
45
+ else
46
+ [BigDecimal(0), epoch]
47
+ end
48
+ end
49
+ total_paid = xs.map { |x| x[0] }.sum
50
+ paid_date = xs.map { |x| x[1] }.max
51
+ if paid_date == epoch
52
+ paid_date = nil
53
+ end
54
+ request_amount = request[:amount]
55
+ request_precision = request[:precision]
56
+ total_paid_rounded_s = CryptoCalc.fixed_length_to_s(total_paid.round(request_precision),
57
+ request_precision)
58
+ remaining = BigDecimal(request[:amount]) - BigDecimal(total_paid_rounded_s)
59
+ remaining_rounded_s = CryptoCalc.fixed_length_to_s(remaining.round(request_precision),
60
+ request_precision)
61
+ n_paid = payments.select { |payment| payment[:paid] == true }.length
62
+ n_unpaid = payments.length - n_paid
63
+ remaining_rounded = BigDecimal(remaining_rounded_s)
64
+ needs_change = remaining_rounded < BigDecimal(0)
65
+ is_fully_paid = remaining_rounded.zero? || needs_change
66
+ is_partially_paid = !is_fully_paid && remaining_rounded < BigDecimal(request_amount)
67
+
68
+ {
69
+ :n_paid => n_paid,
70
+ :n_unpaid => n_unpaid,
71
+ :paid_date => paid_date,
72
+ :amount_paid => total_paid_rounded_s,
73
+ :amount_remaining => remaining_rounded_s,
74
+ :overpaid => needs_change,
75
+ :is_fully_paid => is_fully_paid,
76
+ :is_partially_paid => is_partially_paid
77
+ }
78
+ end
79
+
80
+ def self.apply_fun(f, args)
81
+ args_prime = args.map { |i| BigDecimal(i) }
82
+
83
+ lambdas = {
84
+ 'fst' => lambda { |args| args[0] },
85
+ '1/fst' => lambda { |args| 1.0 / args[0] },
86
+ 'fst,snd' => lambda { |args| [args[0], args[1]] },
87
+ 'fst*snd' => lambda { |args| args[0] * args[1] },
88
+ 'fst/snd' => lambda { |args| args[0] / args[1] },
89
+ 'snd/fst' => lambda { |args| args[1] / args[0] },
90
+ '1/(fst*snd)' => lambda { |args| 1.0 / (args[0] * args[1]) },
91
+ }
92
+
93
+ lambdas[f].call(args_prime)
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,19 @@
1
+ module ExoCrypto
2
+ class Network
3
+ SETTINGS_SECTION = 'crypto_network'
4
+
5
+ @@conf = ExoBasic::Settings.loaded
6
+ ExoBasic::Settings.add_on_load_observer(Network)
7
+
8
+ def self.is_testnet?
9
+ ExoBasic::Settings.try_get_nested_key(@@conf,
10
+ false,
11
+ [Network::SETTINGS_SECTION, 'testnet'])
12
+ end
13
+
14
+ def self.settings_reloaded
15
+ @@conf = ExoBasic::Settings.loaded
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module ExoCrypto
2
+ VERSION = "0.1.0"
3
+ end
data/lib/exocrypto.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'exocrypto/bip44_wallet_provider.rb'
2
+ require 'exocrypto/crypto_calc.rb'
3
+ require 'exocrypto/network.rb'
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: exocrypto
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Dionysios Kakolyris
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-01-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bitcoin-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.0.20
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.0.20
27
+ - !ruby/object:Gem::Dependency
28
+ name: bip44
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.2.18
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.2.18
41
+ - !ruby/object:Gem::Dependency
42
+ name: exobasic
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.1.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.1.0
55
+ description: Exotic Crypto Helpers
56
+ email:
57
+ - contact@exotic.industries
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - README.md
63
+ - lib/exocrypto.rb
64
+ - lib/exocrypto/bip44_wallet_provider.rb
65
+ - lib/exocrypto/crypto_calc.rb
66
+ - lib/exocrypto/network.rb
67
+ - lib/exocrypto/version.rb
68
+ homepage: https://bitbucket.org/vertigoindustries/exotic-crypto
69
+ licenses:
70
+ - MIT
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: 1.3.6
86
+ requirements: []
87
+ rubygems_version: 3.0.6
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: CryptoHelpers
91
+ test_files: []