tapyrus 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.
Files changed (128) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +12 -0
  7. data/CODE_OF_CONDUCT.md +49 -0
  8. data/Gemfile +6 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +100 -0
  11. data/Rakefile +6 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/exe/tapyrusrb-cli +5 -0
  15. data/exe/tapyrusrbd +41 -0
  16. data/lib/openassets/marker_output.rb +20 -0
  17. data/lib/openassets/payload.rb +54 -0
  18. data/lib/openassets/util.rb +28 -0
  19. data/lib/openassets.rb +9 -0
  20. data/lib/tapyrus/base58.rb +38 -0
  21. data/lib/tapyrus/block.rb +77 -0
  22. data/lib/tapyrus/block_header.rb +88 -0
  23. data/lib/tapyrus/bloom_filter.rb +78 -0
  24. data/lib/tapyrus/chain_params.rb +90 -0
  25. data/lib/tapyrus/chainparams/mainnet.yml +41 -0
  26. data/lib/tapyrus/chainparams/regtest.yml +38 -0
  27. data/lib/tapyrus/chainparams/testnet.yml +41 -0
  28. data/lib/tapyrus/constants.rb +195 -0
  29. data/lib/tapyrus/descriptor.rb +147 -0
  30. data/lib/tapyrus/ext_key.rb +337 -0
  31. data/lib/tapyrus/key.rb +296 -0
  32. data/lib/tapyrus/key_path.rb +26 -0
  33. data/lib/tapyrus/logger.rb +42 -0
  34. data/lib/tapyrus/merkle_tree.rb +149 -0
  35. data/lib/tapyrus/message/addr.rb +35 -0
  36. data/lib/tapyrus/message/base.rb +28 -0
  37. data/lib/tapyrus/message/block.rb +46 -0
  38. data/lib/tapyrus/message/block_transaction_request.rb +45 -0
  39. data/lib/tapyrus/message/block_transactions.rb +31 -0
  40. data/lib/tapyrus/message/block_txn.rb +27 -0
  41. data/lib/tapyrus/message/cmpct_block.rb +42 -0
  42. data/lib/tapyrus/message/error.rb +10 -0
  43. data/lib/tapyrus/message/fee_filter.rb +27 -0
  44. data/lib/tapyrus/message/filter_add.rb +28 -0
  45. data/lib/tapyrus/message/filter_clear.rb +17 -0
  46. data/lib/tapyrus/message/filter_load.rb +39 -0
  47. data/lib/tapyrus/message/get_addr.rb +17 -0
  48. data/lib/tapyrus/message/get_block_txn.rb +27 -0
  49. data/lib/tapyrus/message/get_blocks.rb +29 -0
  50. data/lib/tapyrus/message/get_data.rb +21 -0
  51. data/lib/tapyrus/message/get_headers.rb +28 -0
  52. data/lib/tapyrus/message/header_and_short_ids.rb +57 -0
  53. data/lib/tapyrus/message/headers.rb +35 -0
  54. data/lib/tapyrus/message/headers_parser.rb +24 -0
  55. data/lib/tapyrus/message/inv.rb +21 -0
  56. data/lib/tapyrus/message/inventories_parser.rb +23 -0
  57. data/lib/tapyrus/message/inventory.rb +51 -0
  58. data/lib/tapyrus/message/mem_pool.rb +17 -0
  59. data/lib/tapyrus/message/merkle_block.rb +42 -0
  60. data/lib/tapyrus/message/network_addr.rb +63 -0
  61. data/lib/tapyrus/message/not_found.rb +21 -0
  62. data/lib/tapyrus/message/ping.rb +30 -0
  63. data/lib/tapyrus/message/pong.rb +26 -0
  64. data/lib/tapyrus/message/prefilled_tx.rb +29 -0
  65. data/lib/tapyrus/message/reject.rb +46 -0
  66. data/lib/tapyrus/message/send_cmpct.rb +43 -0
  67. data/lib/tapyrus/message/send_headers.rb +16 -0
  68. data/lib/tapyrus/message/tx.rb +30 -0
  69. data/lib/tapyrus/message/ver_ack.rb +17 -0
  70. data/lib/tapyrus/message/version.rb +69 -0
  71. data/lib/tapyrus/message.rb +70 -0
  72. data/lib/tapyrus/mnemonic/wordlist/chinese_simplified.txt +2048 -0
  73. data/lib/tapyrus/mnemonic/wordlist/chinese_traditional.txt +2048 -0
  74. data/lib/tapyrus/mnemonic/wordlist/english.txt +2048 -0
  75. data/lib/tapyrus/mnemonic/wordlist/french.txt +2048 -0
  76. data/lib/tapyrus/mnemonic/wordlist/italian.txt +2048 -0
  77. data/lib/tapyrus/mnemonic/wordlist/japanese.txt +2048 -0
  78. data/lib/tapyrus/mnemonic/wordlist/spanish.txt +2048 -0
  79. data/lib/tapyrus/mnemonic.rb +77 -0
  80. data/lib/tapyrus/network/connection.rb +73 -0
  81. data/lib/tapyrus/network/message_handler.rb +241 -0
  82. data/lib/tapyrus/network/peer.rb +223 -0
  83. data/lib/tapyrus/network/peer_discovery.rb +42 -0
  84. data/lib/tapyrus/network/pool.rb +135 -0
  85. data/lib/tapyrus/network.rb +13 -0
  86. data/lib/tapyrus/node/cli.rb +112 -0
  87. data/lib/tapyrus/node/configuration.rb +38 -0
  88. data/lib/tapyrus/node/spv.rb +79 -0
  89. data/lib/tapyrus/node.rb +7 -0
  90. data/lib/tapyrus/opcodes.rb +178 -0
  91. data/lib/tapyrus/out_point.rb +44 -0
  92. data/lib/tapyrus/rpc/http_server.rb +65 -0
  93. data/lib/tapyrus/rpc/request_handler.rb +150 -0
  94. data/lib/tapyrus/rpc/tapyrus_core_client.rb +72 -0
  95. data/lib/tapyrus/rpc.rb +7 -0
  96. data/lib/tapyrus/script/multisig.rb +92 -0
  97. data/lib/tapyrus/script/script.rb +551 -0
  98. data/lib/tapyrus/script/script_error.rb +111 -0
  99. data/lib/tapyrus/script/script_interpreter.rb +668 -0
  100. data/lib/tapyrus/script/tx_checker.rb +81 -0
  101. data/lib/tapyrus/script_witness.rb +38 -0
  102. data/lib/tapyrus/secp256k1/native.rb +174 -0
  103. data/lib/tapyrus/secp256k1/ruby.rb +123 -0
  104. data/lib/tapyrus/secp256k1.rb +12 -0
  105. data/lib/tapyrus/slip39/share.rb +122 -0
  106. data/lib/tapyrus/slip39/sss.rb +245 -0
  107. data/lib/tapyrus/slip39/wordlist/english.txt +1024 -0
  108. data/lib/tapyrus/slip39.rb +93 -0
  109. data/lib/tapyrus/store/chain_entry.rb +67 -0
  110. data/lib/tapyrus/store/db/level_db.rb +98 -0
  111. data/lib/tapyrus/store/db.rb +9 -0
  112. data/lib/tapyrus/store/spv_chain.rb +101 -0
  113. data/lib/tapyrus/store.rb +9 -0
  114. data/lib/tapyrus/tx.rb +347 -0
  115. data/lib/tapyrus/tx_in.rb +89 -0
  116. data/lib/tapyrus/tx_out.rb +74 -0
  117. data/lib/tapyrus/util.rb +133 -0
  118. data/lib/tapyrus/validation.rb +115 -0
  119. data/lib/tapyrus/version.rb +3 -0
  120. data/lib/tapyrus/wallet/account.rb +151 -0
  121. data/lib/tapyrus/wallet/base.rb +162 -0
  122. data/lib/tapyrus/wallet/db.rb +81 -0
  123. data/lib/tapyrus/wallet/master_key.rb +110 -0
  124. data/lib/tapyrus/wallet.rb +8 -0
  125. data/lib/tapyrus.rb +219 -0
  126. data/tapyrusrb.conf.sample +0 -0
  127. data/tapyrusrb.gemspec +47 -0
  128. metadata +451 -0
@@ -0,0 +1,337 @@
1
+ module Tapyrus
2
+
3
+ # Integers modulo the order of the curve(secp256k1)
4
+ CURVE_ORDER = ECDSA::Group::Secp256k1.order
5
+
6
+ # BIP32 Extended private key
7
+ class ExtKey
8
+
9
+ attr_accessor :ver
10
+ attr_accessor :depth
11
+ attr_accessor :number
12
+ attr_accessor :chain_code
13
+ attr_accessor :key # Tapyrus::Key
14
+ attr_accessor :parent_fingerprint
15
+
16
+ # generate master key from seed.
17
+ # @params [String] seed a seed data with hex format.
18
+ def self.generate_master(seed)
19
+ ext_key = ExtKey.new
20
+ ext_key.depth = ext_key.number = 0
21
+ ext_key.parent_fingerprint = '00000000'
22
+ l = Tapyrus.hmac_sha512('Bitcoin seed', seed.htb)
23
+ left = l[0..31].bth.to_i(16)
24
+ raise 'invalid key' if left >= CURVE_ORDER || left == 0
25
+ ext_key.key = Tapyrus::Key.new(priv_key: l[0..31].bth, key_type: Tapyrus::Key::TYPES[:compressed])
26
+ ext_key.chain_code = l[32..-1]
27
+ ext_key
28
+ end
29
+
30
+ # get ExtPubkey from priv_key
31
+ def ext_pubkey
32
+ k = ExtPubkey.new
33
+ k.depth = depth
34
+ k.number = number
35
+ k.parent_fingerprint = parent_fingerprint
36
+ k.chain_code = chain_code
37
+ k.pubkey = key.pubkey
38
+ k.ver = priv_ver_to_pub_ver
39
+ k
40
+ end
41
+
42
+ # serialize extended private key
43
+ def to_payload
44
+ version.htb << [depth].pack('C') << parent_fingerprint.htb <<
45
+ [number].pack('N') << chain_code << [0x00].pack('C') << key.priv_key.htb
46
+ end
47
+
48
+ # Base58 encoded extended private key
49
+ def to_base58
50
+ h = to_payload.bth
51
+ hex = h + Tapyrus.calc_checksum(h)
52
+ Base58.encode(hex)
53
+ end
54
+
55
+ # get private key(hex)
56
+ def priv
57
+ key.priv_key
58
+ end
59
+
60
+ # get public key(hex)
61
+ def pub
62
+ key.pubkey
63
+ end
64
+
65
+ def hash160
66
+ Tapyrus.hash160(pub)
67
+ end
68
+
69
+ # get address
70
+ def addr
71
+ ext_pubkey.addr
72
+ end
73
+
74
+ # get key identifier
75
+ def identifier
76
+ Tapyrus.hash160(key.pubkey)
77
+ end
78
+
79
+ # get fingerprint
80
+ def fingerprint
81
+ identifier.slice(0..7)
82
+ end
83
+
84
+ # whether hardened key.
85
+ def hardened?
86
+ number >= Tapyrus::HARDENED_THRESHOLD
87
+ end
88
+
89
+ # derive new key
90
+ # @param [Integer] number a child index
91
+ # @param [Boolean] harden whether hardened key or not. If true, 2^31 is added to +number+.
92
+ # @return [Tapyrus::ExtKey] derived new key.
93
+ def derive(number, harden = false)
94
+ number += Tapyrus::HARDENED_THRESHOLD if harden
95
+ new_key = ExtKey.new
96
+ new_key.depth = depth + 1
97
+ new_key.number = number
98
+ new_key.parent_fingerprint = fingerprint
99
+ if number > (Tapyrus::HARDENED_THRESHOLD - 1)
100
+ data = [0x00].pack('C') << key.priv_key.htb << [number].pack('N')
101
+ else
102
+ data = key.pubkey.htb << [number].pack('N')
103
+ end
104
+ l = Tapyrus.hmac_sha512(chain_code, data)
105
+ left = l[0..31].bth.to_i(16)
106
+ raise 'invalid key' if left >= CURVE_ORDER
107
+ child_priv = (left + key.priv_key.to_i(16)) % CURVE_ORDER
108
+ raise 'invalid key ' if child_priv >= CURVE_ORDER
109
+ new_key.key = Tapyrus::Key.new(
110
+ priv_key: child_priv.to_even_length_hex.rjust(64, '0'), key_type: key_type)
111
+ new_key.chain_code = l[32..-1]
112
+ new_key.ver = version
113
+ new_key
114
+ end
115
+
116
+ # get version bytes using serialization format
117
+ def version
118
+ return ExtKey.version_from_purpose(number) if depth == 1
119
+ ver ? ver : Tapyrus.chain_params.extended_privkey_version
120
+ end
121
+
122
+ # get key type defined by BIP-178 using version.
123
+ def key_type
124
+ v = version
125
+ case v
126
+ when Tapyrus.chain_params.bip49_privkey_p2wpkh_p2sh_version
127
+ Tapyrus::Key::TYPES[:p2wpkh_p2sh]
128
+ when Tapyrus.chain_params.bip84_privkey_p2wpkh_version
129
+ Tapyrus::Key::TYPES[:p2wpkh]
130
+ when Tapyrus.chain_params.extended_privkey_version
131
+ Tapyrus::Key::TYPES[:compressed]
132
+ end
133
+ end
134
+
135
+ def ==(other)
136
+ to_payload == other.to_payload
137
+ end
138
+
139
+ def self.parse_from_payload(payload)
140
+ buf = StringIO.new(payload)
141
+ ext_key = ExtKey.new
142
+ ext_key.ver = buf.read(4).bth # version
143
+ raise 'An unsupported version byte was specified.' unless ExtKey.support_version?(ext_key.ver)
144
+ ext_key.depth = buf.read(1).unpack('C').first
145
+ ext_key.parent_fingerprint = buf.read(4).bth
146
+ ext_key.number = buf.read(4).unpack('N').first
147
+ ext_key.chain_code = buf.read(32)
148
+ buf.read(1) # 0x00
149
+ ext_key.key = Tapyrus::Key.new(priv_key: buf.read(32).bth, key_type: Tapyrus::Key::TYPES[:compressed])
150
+ ext_key
151
+ end
152
+
153
+ # import private key from Base58 private key address
154
+ def self.from_base58(address)
155
+ ExtKey.parse_from_payload(Base58.decode(address).htb)
156
+ end
157
+
158
+ # get version bytes from purpose' value.
159
+ def self.version_from_purpose(purpose)
160
+ v = purpose - Tapyrus::HARDENED_THRESHOLD
161
+ case v
162
+ when 49
163
+ Tapyrus.chain_params.bip49_privkey_p2wpkh_p2sh_version
164
+ when 84
165
+ Tapyrus.chain_params.bip84_privkey_p2wpkh_version
166
+ else
167
+ Tapyrus.chain_params.extended_privkey_version
168
+ end
169
+ end
170
+
171
+ # check whether +version+ is supported version bytes.
172
+ def self.support_version?(version)
173
+ p = Tapyrus.chain_params
174
+ [p.bip49_privkey_p2wpkh_p2sh_version, p.bip84_privkey_p2wpkh_version, p.extended_privkey_version].include?(version)
175
+ end
176
+
177
+ # convert privkey version to pubkey version
178
+ def priv_ver_to_pub_ver
179
+ case version
180
+ when Tapyrus.chain_params.bip49_privkey_p2wpkh_p2sh_version
181
+ Tapyrus.chain_params.bip49_pubkey_p2wpkh_p2sh_version
182
+ when Tapyrus.chain_params.bip84_privkey_p2wpkh_version
183
+ Tapyrus.chain_params.bip84_pubkey_p2wpkh_version
184
+ else
185
+ Tapyrus.chain_params.extended_pubkey_version
186
+ end
187
+ end
188
+
189
+ end
190
+
191
+ # BIP-32 Extended public key
192
+ class ExtPubkey
193
+
194
+ attr_accessor :ver
195
+ attr_accessor :depth
196
+ attr_accessor :number
197
+ attr_accessor :chain_code
198
+ attr_accessor :pubkey # hex format
199
+ attr_accessor :parent_fingerprint
200
+
201
+ # serialize extended pubkey
202
+ def to_payload
203
+ version.htb << [depth].pack('C') <<
204
+ parent_fingerprint.htb << [number].pack('N') << chain_code << pub.htb
205
+ end
206
+
207
+ def pub
208
+ pubkey
209
+ end
210
+
211
+ def hash160
212
+ Tapyrus.hash160(pub)
213
+ end
214
+
215
+ # get address
216
+ def addr
217
+ case version
218
+ when Tapyrus.chain_params.bip49_pubkey_p2wpkh_p2sh_version
219
+ key.to_nested_p2wpkh
220
+ when Tapyrus.chain_params.bip84_pubkey_p2wpkh_version
221
+ key.to_p2wpkh
222
+ else
223
+ key.to_p2pkh
224
+ end
225
+ end
226
+
227
+ # get key object
228
+ # @return [Tapyrus::Key]
229
+ def key
230
+ Tapyrus::Key.new(pubkey: pubkey, key_type: key_type)
231
+ end
232
+
233
+ # get key identifier
234
+ def identifier
235
+ Tapyrus.hash160(pub)
236
+ end
237
+
238
+ # get fingerprint
239
+ def fingerprint
240
+ identifier.slice(0..7)
241
+ end
242
+
243
+ # Base58 encoded extended pubkey
244
+ def to_base58
245
+ h = to_payload.bth
246
+ hex = h + Tapyrus.calc_checksum(h)
247
+ Base58.encode(hex)
248
+ end
249
+
250
+ # whether hardened key.
251
+ def hardened?
252
+ number >= Tapyrus::HARDENED_THRESHOLD
253
+ end
254
+
255
+ # derive child key
256
+ def derive(number)
257
+ new_key = ExtPubkey.new
258
+ new_key.depth = depth + 1
259
+ new_key.number = number
260
+ new_key.parent_fingerprint = fingerprint
261
+ raise 'hardened key is not support' if number > (Tapyrus::HARDENED_THRESHOLD - 1)
262
+ data = pub.htb << [number].pack('N')
263
+ l = Tapyrus.hmac_sha512(chain_code, data)
264
+ left = l[0..31].bth.to_i(16)
265
+ raise 'invalid key' if left >= CURVE_ORDER
266
+ p1 = Tapyrus::Secp256k1::GROUP.generator.multiply_by_scalar(left)
267
+ p2 = Tapyrus::Key.new(pubkey: pubkey, key_type: key_type).to_point
268
+ new_key.pubkey = ECDSA::Format::PointOctetString.encode(p1 + p2, compression: true).bth
269
+ new_key.chain_code = l[32..-1]
270
+ new_key.ver = version
271
+ new_key
272
+ end
273
+
274
+ # get version bytes using serialization format
275
+ def version
276
+ return ExtPubkey.version_from_purpose(number) if depth == 1
277
+ ver ? ver : Tapyrus.chain_params.extended_pubkey_version
278
+ end
279
+
280
+ # get key type defined by BIP-178 using version.
281
+ def key_type
282
+ v = version
283
+ case v
284
+ when Tapyrus.chain_params.bip49_pubkey_p2wpkh_p2sh_version
285
+ Tapyrus::Key::TYPES[:p2wpkh_p2sh]
286
+ when Tapyrus.chain_params.bip84_pubkey_p2wpkh_version
287
+ Tapyrus::Key::TYPES[:p2wpkh]
288
+ when Tapyrus.chain_params.extended_pubkey_version
289
+ Tapyrus::Key::TYPES[:compressed]
290
+ end
291
+ end
292
+
293
+ def ==(other)
294
+ to_payload == other.to_payload
295
+ end
296
+
297
+ def self.parse_from_payload(payload)
298
+ buf = StringIO.new(payload)
299
+ ext_pubkey = ExtPubkey.new
300
+ ext_pubkey.ver = buf.read(4).bth # version
301
+ raise 'An unsupported version byte was specified.' unless ExtPubkey.support_version?(ext_pubkey.ver)
302
+ ext_pubkey.depth = buf.read(1).unpack('C').first
303
+ ext_pubkey.parent_fingerprint = buf.read(4).bth
304
+ ext_pubkey.number = buf.read(4).unpack('N').first
305
+ ext_pubkey.chain_code = buf.read(32)
306
+ ext_pubkey.pubkey = buf.read(33).bth
307
+ ext_pubkey
308
+ end
309
+
310
+
311
+ # import pub key from Base58 private key address
312
+ def self.from_base58(address)
313
+ ExtPubkey.parse_from_payload(Base58.decode(address).htb)
314
+ end
315
+
316
+ # get version bytes from purpose' value.
317
+ def self.version_from_purpose(purpose)
318
+ v = purpose - Tapyrus::HARDENED_THRESHOLD
319
+ case v
320
+ when 49
321
+ Tapyrus.chain_params.bip49_pubkey_p2wpkh_p2sh_version
322
+ when 84
323
+ Tapyrus.chain_params.bip84_pubkey_p2wpkh_version
324
+ else
325
+ Tapyrus.chain_params.extended_pubkey_version
326
+ end
327
+ end
328
+
329
+ # check whether +version+ is supported version bytes.
330
+ def self.support_version?(version)
331
+ p = Tapyrus.chain_params
332
+ [p.bip49_pubkey_p2wpkh_p2sh_version, p.bip84_pubkey_p2wpkh_version, p.extended_pubkey_version].include?(version)
333
+ end
334
+
335
+ end
336
+
337
+ end
@@ -0,0 +1,296 @@
1
+ # Porting part of the code from bitcoin-ruby. see the license.
2
+ # https://github.com/lian/bitcoin-ruby/blob/master/COPYING
3
+
4
+ module Tapyrus
5
+
6
+ # tapyrus key class
7
+ class Key
8
+
9
+ PUBLIC_KEY_SIZE = 65
10
+ COMPRESSED_PUBLIC_KEY_SIZE = 33
11
+ SIGNATURE_SIZE = 72
12
+ COMPACT_SIGNATURE_SIZE = 65
13
+
14
+ attr_accessor :priv_key
15
+ attr_accessor :pubkey
16
+ attr_accessor :key_type
17
+ attr_reader :secp256k1_module
18
+
19
+ TYPES = {uncompressed: 0x00, compressed: 0x01, p2pkh: 0x10, p2wpkh: 0x11, p2wpkh_p2sh: 0x12}
20
+
21
+ MIN_PRIV_KEY_MOD_ORDER = 0x01
22
+ # Order of secp256k1's generator minus 1.
23
+ MAX_PRIV_KEY_MOD_ORDER = ECDSA::Group::Secp256k1.order - 1
24
+
25
+ # initialize private key
26
+ # @param [String] priv_key a private key with hex format.
27
+ # @param [String] pubkey a public key with hex format.
28
+ # @param [Integer] key_type a key type which determine address type.
29
+ # @param [Boolean] compressed [Deprecated] whether public key is compressed.
30
+ # @return [Tapyrus::Key] a key object.
31
+ def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true)
32
+ puts "[Warning] Use key_type parameter instead of compressed. compressed parameter removed in the future." if key_type.nil? && !compressed.nil? && pubkey.nil?
33
+ if key_type
34
+ @key_type = key_type
35
+ compressed = @key_type != TYPES[:uncompressed]
36
+ else
37
+ @key_type = compressed ? TYPES[:compressed] : TYPES[:uncompressed]
38
+ end
39
+ @secp256k1_module = Tapyrus.secp_impl
40
+ @priv_key = priv_key
41
+ if @priv_key
42
+ raise ArgumentError, 'private key is not on curve' unless validate_private_key_range(@priv_key)
43
+ end
44
+ if pubkey
45
+ @pubkey = pubkey
46
+ else
47
+ @pubkey = generate_pubkey(priv_key, compressed: compressed) if priv_key
48
+ end
49
+ end
50
+
51
+ # generate key pair
52
+ def self.generate(key_type = TYPES[:compressed])
53
+ priv_key, pubkey = Tapyrus.secp_impl.generate_key_pair
54
+ new(priv_key: priv_key, pubkey: pubkey, key_type: key_type)
55
+ end
56
+
57
+ # import private key from wif format
58
+ # https://en.bitcoin.it/wiki/Wallet_import_format
59
+ def self.from_wif(wif)
60
+ hex = Base58.decode(wif)
61
+ raise ArgumentError, 'data is too short' if hex.htb.bytesize < 4
62
+ version = hex[0..1]
63
+ data = hex[2...-8].htb
64
+ checksum = hex[-8..-1]
65
+ raise ArgumentError, 'invalid version' unless version == Tapyrus.chain_params.privkey_version
66
+ raise ArgumentError, 'invalid checksum' unless Tapyrus.calc_checksum(version + data.bth) == checksum
67
+ key_len = data.bytesize
68
+ if key_len == COMPRESSED_PUBLIC_KEY_SIZE && data[-1].unpack('C').first == 1
69
+ key_type = TYPES[:compressed]
70
+ data = data[0..-2]
71
+ elsif key_len == 32
72
+ key_type = TYPES[:uncompressed]
73
+ else
74
+ raise ArgumentError, 'Wrong number of bytes for a private key, not 32 or 33'
75
+ end
76
+ new(priv_key: data.bth, key_type: key_type)
77
+ end
78
+
79
+ # export private key with wif format
80
+ def to_wif
81
+ version = Tapyrus.chain_params.privkey_version
82
+ hex = version + priv_key
83
+ hex += '01' if compressed?
84
+ hex += Tapyrus.calc_checksum(hex)
85
+ Base58.encode(hex)
86
+ end
87
+
88
+ # sign +data+ with private key
89
+ # @param [String] data a data to be signed with binary format
90
+ # @param [Boolean] low_r flag to apply low-R.
91
+ # @param [String] extra_entropy the extra entropy for rfc6979.
92
+ # @return [String] signature data with binary format
93
+ def sign(data, low_r = true, extra_entropy = nil)
94
+ sig = secp256k1_module.sign_data(data, priv_key, extra_entropy)
95
+ if low_r && !sig_has_low_r?(sig)
96
+ counter = 1
97
+ until sig_has_low_r?(sig)
98
+ extra_entropy = [counter].pack('I*').bth.ljust(64, '0').htb
99
+ sig = secp256k1_module.sign_data(data, priv_key, extra_entropy)
100
+ counter += 1
101
+ end
102
+ end
103
+ sig
104
+ end
105
+
106
+ # verify signature using public key
107
+ # @param [String] sig signature data with binary format
108
+ # @param [String] origin original message
109
+ # @return [Boolean] verify result
110
+ def verify(sig, origin)
111
+ return false unless valid_pubkey?
112
+ begin
113
+ sig = ecdsa_signature_parse_der_lax(sig)
114
+ secp256k1_module.verify_sig(origin, sig, pubkey)
115
+ rescue Exception
116
+ false
117
+ end
118
+ end
119
+
120
+ # get hash160 public key.
121
+ def hash160
122
+ Tapyrus.hash160(pubkey)
123
+ end
124
+
125
+ # get pay to pubkey hash address
126
+ # @deprecated
127
+ def to_p2pkh
128
+ Tapyrus::Script.to_p2pkh(hash160).addresses.first
129
+ end
130
+
131
+ # get pay to witness pubkey hash address
132
+ # @deprecated
133
+ def to_p2wpkh
134
+ Tapyrus::Script.to_p2wpkh(hash160).addresses.first
135
+ end
136
+
137
+ # get p2wpkh address nested in p2sh.
138
+ # @deprecated
139
+ def to_nested_p2wpkh
140
+ Tapyrus::Script.to_p2wpkh(hash160).to_p2sh.addresses.first
141
+ end
142
+
143
+ def compressed?
144
+ key_type != TYPES[:uncompressed]
145
+ end
146
+
147
+ # generate pubkey ec point
148
+ # @return [ECDSA::Point]
149
+ def to_point
150
+ p = pubkey
151
+ p ||= generate_pubkey(priv_key, compressed: compressed)
152
+ ECDSA::Format::PointOctetString.decode(p.htb, Tapyrus::Secp256k1::GROUP)
153
+ end
154
+
155
+ # check +pubkey+ (hex) is compress or uncompress pubkey.
156
+ def self.compress_or_uncompress_pubkey?(pubkey)
157
+ p = pubkey.htb
158
+ return false if p.bytesize < COMPRESSED_PUBLIC_KEY_SIZE
159
+ case p[0]
160
+ when "\x04"
161
+ return false unless p.bytesize == PUBLIC_KEY_SIZE
162
+ when "\x02", "\x03"
163
+ return false unless p.bytesize == COMPRESSED_PUBLIC_KEY_SIZE
164
+ else
165
+ return false
166
+ end
167
+ true
168
+ end
169
+
170
+ # check +pubkey+ (hex) is compress pubkey.
171
+ def self.compress_pubkey?(pubkey)
172
+ p = pubkey.htb
173
+ p.bytesize == COMPRESSED_PUBLIC_KEY_SIZE && ["\x02", "\x03"].include?(p[0])
174
+ end
175
+
176
+ # check +sig+ is low.
177
+ def self.low_signature?(sig)
178
+ s = sig.unpack('C*')
179
+ len_r = s[3]
180
+ len_s = s[5 + len_r]
181
+ val_s = s.slice(6 + len_r, len_s)
182
+ max_mod_half_order = [
183
+ 0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
184
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
185
+ 0x5d,0x57,0x6e,0x73,0x57,0xa4,0x50,0x1d,
186
+ 0xdf,0xe9,0x2f,0x46,0x68,0x1b,0x20,0xa0]
187
+ compare_big_endian(val_s, [0]) > 0 &&
188
+ compare_big_endian(val_s, max_mod_half_order) <= 0
189
+ end
190
+
191
+
192
+ # check +sig+ is correct der encoding.
193
+ # This function is consensus-critical since BIP66.
194
+ def self.valid_signature_encoding?(sig)
195
+ return false if sig.bytesize < 9 || sig.bytesize > 73 # Minimum and maximum size check
196
+
197
+ s = sig.unpack('C*')
198
+
199
+ return false if s[0] != 0x30 || s[1] != s.size - 3 # A signature is of type 0x30 (compound). Make sure the length covers the entire signature.
200
+
201
+ len_r = s[3]
202
+ return false if 5 + len_r >= s.size # Make sure the length of the S element is still inside the signature.
203
+
204
+ len_s = s[5 + len_r]
205
+ return false unless len_r + len_s + 7 == s.size #Verify that the length of the signature matches the sum of the length of the elements.
206
+
207
+ return false unless s[2] == 0x02 # Check whether the R element is an integer.
208
+
209
+ return false if len_r == 0 # Zero-length integers are not allowed for R.
210
+
211
+ return false unless s[4] & 0x80 == 0 # Negative numbers are not allowed for R.
212
+
213
+ # Null bytes at the start of R are not allowed, unless R would otherwise be interpreted as a negative number.
214
+ return false if len_r > 1 && (s[4] == 0x00) && (s[5] & 0x80 == 0)
215
+
216
+ return false unless s[len_r + 4] == 0x02 # Check whether the S element is an integer.
217
+
218
+ return false if len_s == 0 # Zero-length integers are not allowed for S.
219
+ return false unless (s[len_r + 6] & 0x80) == 0 # Negative numbers are not allowed for S.
220
+
221
+ # Null bytes at the start of S are not allowed, unless S would otherwise be interpreted as a negative number.
222
+ return false if len_s > 1 && (s[len_r + 6] == 0x00) && (s[len_r + 7] & 0x80 == 0)
223
+
224
+ true
225
+ end
226
+
227
+ # fully validate whether this is a valid public key (more expensive than IsValid())
228
+ def fully_valid_pubkey?
229
+ return false unless valid_pubkey?
230
+ begin
231
+ point = ECDSA::Format::PointOctetString.decode(pubkey.htb, ECDSA::Group::Secp256k1)
232
+ ECDSA::Group::Secp256k1.valid_public_key?(point)
233
+ rescue ECDSA::Format::DecodeError
234
+ false
235
+ end
236
+ end
237
+
238
+ private
239
+
240
+ def self.compare_big_endian(c1, c2)
241
+ c1, c2 = c1.dup, c2.dup # Clone the arrays
242
+
243
+ while c1.size > c2.size
244
+ return 1 if c1.shift > 0
245
+ end
246
+
247
+ while c2.size > c1.size
248
+ return -1 if c2.shift > 0
249
+ end
250
+
251
+ c1.size.times{|idx| return c1[idx] - c2[idx] if c1[idx] != c2[idx] }
252
+ 0
253
+ end
254
+
255
+ # generate publick key from private key
256
+ # @param [String] privkey a private key with string format
257
+ # @param [Boolean] compressed pubkey compressed?
258
+ # @return [String] a pubkey which generate from privkey
259
+ def generate_pubkey(privkey, compressed: true)
260
+ @secp256k1_module.generate_pubkey(privkey, compressed: compressed)
261
+ end
262
+
263
+ # check private key range.
264
+ def validate_private_key_range(private_key)
265
+ value = private_key.to_i(16)
266
+ MIN_PRIV_KEY_MOD_ORDER <= value && value <= MAX_PRIV_KEY_MOD_ORDER
267
+ end
268
+
269
+ # Supported violations include negative integers, excessive padding, garbage
270
+ # at the end, and overly long length descriptors. This is safe to use in
271
+ # Bitcoin because since the activation of BIP66, signatures are verified to be
272
+ # strict DER before being passed to this module, and we know it supports all
273
+ # violations present in the blockchain before that point.
274
+ def ecdsa_signature_parse_der_lax(sig)
275
+ sig_array = sig.unpack('C*')
276
+ len_r = sig_array[3]
277
+ r = sig_array[4...(len_r+4)].pack('C*').bth
278
+ len_s = sig_array[len_r + 5]
279
+ s = sig_array[(len_r + 6)...(len_r + 6 + len_s)].pack('C*').bth
280
+ ECDSA::Signature.new(r.to_i(16), s.to_i(16)).to_der
281
+ end
282
+
283
+ def valid_pubkey?
284
+ !pubkey.nil? && pubkey.size > 0
285
+ end
286
+
287
+ # check whether the signature is low-R
288
+ # @param [String] sig the signature data
289
+ # @return [Boolean] result
290
+ def sig_has_low_r?(sig)
291
+ sig[3].bth.to_i(16) == 0x20 && sig[4].bth.to_i(16) < 0x80
292
+ end
293
+
294
+ end
295
+
296
+ end
@@ -0,0 +1,26 @@
1
+ module Tapyrus
2
+ module KeyPath
3
+
4
+ # key path convert an array of derive number
5
+ # @param [String] path_string
6
+ # @return [Array[Integer]] key path numbers.
7
+ def parse_key_path(path_string)
8
+ path_string.split('/').map.with_index do|p, index|
9
+ if index == 0
10
+ raise ArgumentError.new("#{path_string} is invalid format.") unless p == 'm'
11
+ next
12
+ end
13
+ raise ArgumentError.new("#{path_string} is invalid format.") unless p.delete("'") =~ /^[0-9]+$/
14
+ (p[-1] == "'" ? p.delete("'").to_i + Tapyrus::HARDENED_THRESHOLD : p.to_i)
15
+ end[1..-1]
16
+ end
17
+
18
+ # key path numbers convert to path string.
19
+ # @param [Array[Integer]] key path numbers.
20
+ # @return [String] path string.
21
+ def to_key_path(numbers)
22
+ "m/#{numbers.map{|p| p >= Tapyrus::HARDENED_THRESHOLD ? "#{p - Tapyrus::HARDENED_THRESHOLD}'" : p.to_s}.join('/')}"
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,42 @@
1
+ require 'logger'
2
+
3
+ module Tapyrus
4
+
5
+ # Simple Logger module
6
+ module Logger
7
+
8
+ Format = "%s, [%s#%d #%d] %5s -- %s: %s\n".freeze
9
+
10
+ module_function
11
+
12
+ # Create a logger with given +name+.log in $HOME/.tapyrusrb/log.
13
+ def create(name, level = ::Logger::INFO)
14
+ dir = "#{Tapyrus.base_dir}/log"
15
+ FileUtils.mkdir_p(dir)
16
+ logger = ::Logger.new(dir + "/#{name}.log", 10)
17
+ logger.level = level
18
+ logger.formatter = proc do |severity, datetime, progname, msg|
19
+ Format % [severity[0..0], format_datetime(datetime), $$,
20
+ Thread.current.object_id, severity, progname, msg2str(msg)]
21
+ end
22
+ logger
23
+ end
24
+
25
+ def self.msg2str(msg)
26
+ case msg
27
+ when ::String
28
+ msg
29
+ when ::Exception
30
+ "#{ msg.message } (#{ msg.class })\n" <<
31
+ (msg.backtrace || []).join("\n")
32
+ else
33
+ msg.inspect
34
+ end
35
+ end
36
+
37
+ def format_datetime(time)
38
+ time.strftime(@datetime_format || "%Y-%m-%dT%H:%M:%S.%6N ".freeze)
39
+ end
40
+
41
+ end
42
+ end