exocrypto 0.1.0

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