tapyrus 0.1.0

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