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
data/lib/tapyrus/tx.rb ADDED
@@ -0,0 +1,347 @@
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
+ # Transaction class
7
+ class Tx
8
+
9
+ MAX_STANDARD_VERSION = 2
10
+
11
+ # The maximum weight for transactions we're willing to relay/mine
12
+ MAX_STANDARD_TX_WEIGHT = 400000
13
+
14
+ MARKER = 0x00
15
+ FLAG = 0x01
16
+
17
+ attr_accessor :version
18
+ attr_accessor :marker
19
+ attr_accessor :flag
20
+ attr_reader :inputs
21
+ attr_reader :outputs
22
+ attr_accessor :lock_time
23
+
24
+ def initialize
25
+ @inputs = []
26
+ @outputs = []
27
+ @version = 1
28
+ @lock_time = 0
29
+ end
30
+
31
+ alias_method :in, :inputs
32
+ alias_method :out, :outputs
33
+
34
+ def self.parse_from_payload(payload, non_witness: false)
35
+ buf = payload.is_a?(String) ? StringIO.new(payload) : payload
36
+ tx = new
37
+ tx.version = buf.read(4).unpack('V').first
38
+
39
+ in_count = Tapyrus.unpack_var_int_from_io(buf)
40
+ witness = false
41
+ if in_count.zero? && !non_witness
42
+ tx.marker = 0
43
+ tx.flag = buf.read(1).unpack('c').first
44
+ if tx.flag.zero?
45
+ buf.pos -= 1
46
+ else
47
+ in_count = Tapyrus.unpack_var_int_from_io(buf)
48
+ witness = true
49
+ end
50
+ end
51
+
52
+ in_count.times do
53
+ tx.inputs << TxIn.parse_from_payload(buf)
54
+ end
55
+
56
+ out_count = Tapyrus.unpack_var_int_from_io(buf)
57
+ out_count.times do
58
+ tx.outputs << TxOut.parse_from_payload(buf)
59
+ end
60
+
61
+ if witness
62
+ in_count.times do |i|
63
+ tx.inputs[i].script_witness = Tapyrus::ScriptWitness.parse_from_payload(buf)
64
+ end
65
+ end
66
+
67
+ tx.lock_time = buf.read(4).unpack('V').first
68
+
69
+ tx
70
+ end
71
+
72
+ def hash
73
+ to_payload.bth.to_i(16)
74
+ end
75
+
76
+ def tx_hash
77
+ Tapyrus.double_sha256(serialize_old_format).bth
78
+ end
79
+
80
+ def txid
81
+ buf = [version].pack('V')
82
+ buf << Tapyrus.pack_var_int(inputs.length) << inputs.map{|i|i.to_payload(use_malfix: true)}.join
83
+ buf << Tapyrus.pack_var_int(outputs.length) << outputs.map(&:to_payload).join
84
+ buf << [lock_time].pack('V')
85
+ Tapyrus.double_sha256(buf).reverse.bth
86
+ end
87
+
88
+ def witness_hash
89
+ Tapyrus.double_sha256(to_payload).bth
90
+ end
91
+
92
+ def wtxid
93
+ witness_hash.rhex
94
+ end
95
+
96
+ # get the witness commitment of coinbase tx.
97
+ # if this tx does not coinbase or not have commitment, return nil.
98
+ def witness_commitment
99
+ return nil unless coinbase_tx?
100
+ outputs.each do |output|
101
+ commitment = output.script_pubkey.witness_commitment
102
+ return commitment if commitment
103
+ end
104
+ nil
105
+ end
106
+
107
+ def to_payload
108
+ witness? ? serialize_witness_format : serialize_old_format
109
+ end
110
+
111
+ # convert tx to hex format.
112
+ # @return [String] tx with hex format.
113
+ def to_hex
114
+ to_payload.bth
115
+ end
116
+
117
+ def coinbase_tx?
118
+ inputs.length == 1 && inputs.first.coinbase?
119
+ end
120
+
121
+ def witness?
122
+ !inputs.find { |i| !i.script_witness.empty? }.nil?
123
+ end
124
+
125
+ def ==(other)
126
+ to_payload == other.to_payload
127
+ end
128
+
129
+ # serialize tx with old tx format
130
+ def serialize_old_format
131
+ buf = [version].pack('V')
132
+ buf << Tapyrus.pack_var_int(inputs.length) << inputs.map(&:to_payload).join
133
+ buf << Tapyrus.pack_var_int(outputs.length) << outputs.map(&:to_payload).join
134
+ buf << [lock_time].pack('V')
135
+ buf
136
+ end
137
+
138
+ # serialize tx with segwit tx format
139
+ # https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki
140
+ def serialize_witness_format
141
+ buf = [version, MARKER, FLAG].pack('Vcc')
142
+ buf << Tapyrus.pack_var_int(inputs.length) << inputs.map(&:to_payload).join
143
+ buf << Tapyrus.pack_var_int(outputs.length) << outputs.map(&:to_payload).join
144
+ buf << witness_payload << [lock_time].pack('V')
145
+ buf
146
+ end
147
+
148
+ def witness_payload
149
+ inputs.map { |i| i.script_witness.to_payload }.join
150
+ end
151
+
152
+ # check this tx is standard.
153
+ def standard?
154
+ return false if version > MAX_STANDARD_VERSION
155
+ return false if weight > MAX_STANDARD_TX_WEIGHT
156
+ inputs.each do |i|
157
+ # Biggest 'standard' txin is a 15-of-15 P2SH multisig with compressed keys (remember the 520 byte limit on redeemScript size).
158
+ # That works out to a (15*(33+1))+3=513 byte redeemScript, 513+1+15*(73+1)+3=1627
159
+ # bytes of scriptSig, which we round off to 1650 bytes for some minor future-proofing.
160
+ # That's also enough to spend a 20-of-20 CHECKMULTISIG scriptPubKey, though such a scriptPubKey is not considered standard.
161
+ return false if i.script_sig.size > 1650
162
+ return false unless i.script_sig.push_only?
163
+ end
164
+ data_count = 0
165
+ outputs.each do |o|
166
+ return false unless o.script_pubkey.standard?
167
+ data_count += 1 if o.script_pubkey.op_return?
168
+ # TODO add non P2SH multisig relay(permitbaremultisig)
169
+ return false if o.dust?
170
+ end
171
+ return false if data_count > 1
172
+ true
173
+ end
174
+
175
+ # The serialized transaction size
176
+ def size
177
+ to_payload.bytesize
178
+ end
179
+
180
+ # The virtual transaction size (differs from size for witness transactions)
181
+ def vsize
182
+ (weight.to_f / 4).ceil
183
+ end
184
+
185
+ # calculate tx weight
186
+ # weight = (legacy tx payload) * 3 + (witness tx payload)
187
+ def weight
188
+ if witness?
189
+ serialize_old_format.bytesize * (WITNESS_SCALE_FACTOR - 1) + serialize_witness_format.bytesize
190
+ else
191
+ serialize_old_format.bytesize * WITNESS_SCALE_FACTOR
192
+ end
193
+ end
194
+
195
+ # get signature hash
196
+ # @param [Integer] input_index input index.
197
+ # @param [Integer] hash_type signature hash type
198
+ # @param [Tapyrus::Script] output_script script pubkey or script code. if script pubkey is P2WSH, set witness script to this.
199
+ # @param [Integer] amount tapyrus amount locked in input. required for witness input only.
200
+ # @param [Integer] skip_separator_index If output_script is P2WSH and output_script contains any OP_CODESEPARATOR,
201
+ # the script code needs is the witnessScript but removing everything up to and including the last executed OP_CODESEPARATOR before the signature checking opcode being executed.
202
+ def sighash_for_input(input_index, output_script, hash_type: SIGHASH_TYPE[:all],
203
+ sig_version: :base, amount: nil, skip_separator_index: 0)
204
+ raise ArgumentError, 'input_index must be specified.' unless input_index
205
+ raise ArgumentError, 'does not exist input corresponding to input_index.' if input_index >= inputs.size
206
+ raise ArgumentError, 'script_pubkey must be specified.' unless output_script
207
+ raise ArgumentError, 'unsupported sig version specified.' unless SIG_VERSION.include?(sig_version)
208
+
209
+ if sig_version == :witness_v0 || Tapyrus.chain_params.fork_chain?
210
+ raise ArgumentError, 'amount must be specified.' unless amount
211
+ sighash_for_witness(input_index, output_script, hash_type, amount, skip_separator_index)
212
+ else
213
+ sighash_for_legacy(input_index, output_script, hash_type)
214
+ end
215
+ end
216
+
217
+ # verify input signature.
218
+ # @param [Integer] input_index
219
+ # @param [Tapyrus::Script] script_pubkey the script pubkey for target input.
220
+ # @param [Integer] amount the amount of tapyrus, require for witness program only.
221
+ # @param [Array] flags the flags used when execute script interpreter.
222
+ def verify_input_sig(input_index, script_pubkey, amount: nil, flags: STANDARD_SCRIPT_VERIFY_FLAGS)
223
+ script_sig = inputs[input_index].script_sig
224
+ has_witness = inputs[input_index].has_witness?
225
+
226
+ if script_pubkey.p2sh?
227
+ flags << SCRIPT_VERIFY_P2SH
228
+ redeem_script = Script.parse_from_payload(script_sig.chunks.last)
229
+ script_pubkey = redeem_script if redeem_script.p2wpkh?
230
+ end
231
+
232
+ if has_witness || Tapyrus.chain_params.fork_chain?
233
+ verify_input_sig_for_witness(input_index, script_pubkey, amount, flags)
234
+ else
235
+ verify_input_sig_for_legacy(input_index, script_pubkey, flags)
236
+ end
237
+ end
238
+
239
+ def to_h
240
+ {
241
+ txid: txid, hash: witness_hash.rhex, version: version, size: size, vsize: vsize, locktime: lock_time,
242
+ vin: inputs.map(&:to_h), vout: outputs.map.with_index{|tx_out, index| tx_out.to_h.merge({n: index})}
243
+ }
244
+ end
245
+
246
+ # Verify transaction validity.
247
+ # @return [Boolean] whether this tx is valid or not.
248
+ def valid?
249
+ state = Tapyrus::ValidationState.new
250
+ validation = Tapyrus::Validation.new
251
+ validation.check_tx(self, state) && state.valid?
252
+ end
253
+
254
+ private
255
+
256
+ # generate sighash with legacy format
257
+ def sighash_for_legacy(index, script_code, hash_type)
258
+ ins = inputs.map.with_index do |i, idx|
259
+ if idx == index
260
+ i.to_payload(script_code.delete_opcode(Tapyrus::Opcodes::OP_CODESEPARATOR))
261
+ else
262
+ case hash_type & 0x1f
263
+ when SIGHASH_TYPE[:none], SIGHASH_TYPE[:single]
264
+ i.to_payload(Tapyrus::Script.new, 0)
265
+ else
266
+ i.to_payload(Tapyrus::Script.new)
267
+ end
268
+ end
269
+ end
270
+
271
+ outs = outputs.map(&:to_payload)
272
+ out_size = Tapyrus.pack_var_int(outputs.size)
273
+
274
+ case hash_type & 0x1f
275
+ when SIGHASH_TYPE[:none]
276
+ outs = ''
277
+ out_size = Tapyrus.pack_var_int(0)
278
+ when SIGHASH_TYPE[:single]
279
+ return "\x01".ljust(32, "\x00") if index >= outputs.size
280
+ outs = outputs[0...(index + 1)].map.with_index { |o, idx| (idx == index) ? o.to_payload : o.to_empty_payload }.join
281
+ out_size = Tapyrus.pack_var_int(index + 1)
282
+ end
283
+
284
+ if hash_type & SIGHASH_TYPE[:anyonecanpay] != 0
285
+ ins = [ins[index]]
286
+ end
287
+
288
+ buf = [[version].pack('V'), Tapyrus.pack_var_int(ins.size),
289
+ ins, out_size, outs, [lock_time, hash_type].pack('VV')].join
290
+
291
+ Tapyrus.double_sha256(buf)
292
+ end
293
+
294
+ # generate sighash with BIP-143 format
295
+ # https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
296
+ def sighash_for_witness(index, script_pubkey_or_script_code, hash_type, amount, skip_separator_index)
297
+ hash_prevouts = Tapyrus.double_sha256(inputs.map{|i|i.out_point.to_payload}.join)
298
+ hash_sequence = Tapyrus.double_sha256(inputs.map{|i|[i.sequence].pack('V')}.join)
299
+ outpoint = inputs[index].out_point.to_payload
300
+ amount = [amount].pack('Q')
301
+ nsequence = [inputs[index].sequence].pack('V')
302
+ hash_outputs = Tapyrus.double_sha256(outputs.map{|o|o.to_payload}.join)
303
+
304
+ script_code = script_pubkey_or_script_code.to_script_code(skip_separator_index)
305
+
306
+ case (hash_type & 0x1f)
307
+ when SIGHASH_TYPE[:single]
308
+ hash_outputs = index >= outputs.size ? "\x00".ljust(32, "\x00") : Tapyrus.double_sha256(outputs[index].to_payload)
309
+ hash_sequence = "\x00".ljust(32, "\x00")
310
+ when SIGHASH_TYPE[:none]
311
+ hash_sequence = hash_outputs = "\x00".ljust(32, "\x00")
312
+ end
313
+
314
+ if (hash_type & SIGHASH_TYPE[:anyonecanpay]) != 0
315
+ hash_prevouts = hash_sequence ="\x00".ljust(32, "\x00")
316
+ end
317
+ hash_type |= (Tapyrus.chain_params.fork_id << 8) if Tapyrus.chain_params.fork_chain?
318
+ buf = [ [version].pack('V'), hash_prevouts, hash_sequence, outpoint,
319
+ script_code ,amount, nsequence, hash_outputs, [@lock_time, hash_type].pack('VV')].join
320
+ Tapyrus.double_sha256(buf)
321
+ end
322
+
323
+ # verify input signature for legacy tx.
324
+ def verify_input_sig_for_legacy(input_index, script_pubkey, flags)
325
+ script_sig = inputs[input_index].script_sig
326
+ checker = Tapyrus::TxChecker.new(tx: self, input_index: input_index)
327
+ interpreter = Tapyrus::ScriptInterpreter.new(checker: checker, flags: flags)
328
+
329
+ interpreter.verify_script(script_sig, script_pubkey)
330
+ end
331
+
332
+ # verify input signature for witness tx.
333
+ def verify_input_sig_for_witness(input_index, script_pubkey, amount, flags)
334
+ flags |= SCRIPT_VERIFY_WITNESS
335
+ flags |= SCRIPT_VERIFY_WITNESS_PUBKEYTYPE
336
+ checker = Tapyrus::TxChecker.new(tx: self, input_index: input_index, amount: amount)
337
+ interpreter = Tapyrus::ScriptInterpreter.new(checker: checker, flags: flags)
338
+ i = inputs[input_index]
339
+
340
+ script_sig = i.script_sig
341
+ witness = i.script_witness
342
+ interpreter.verify_script(script_sig, script_pubkey, witness)
343
+ end
344
+
345
+ end
346
+
347
+ end
@@ -0,0 +1,89 @@
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
+ # transaction input
7
+ class TxIn
8
+
9
+ attr_accessor :out_point
10
+ attr_accessor :script_sig
11
+ attr_accessor :sequence
12
+ attr_accessor :script_witness
13
+
14
+ # Setting nSequence to this value for every input in a transaction disables nLockTime.
15
+ SEQUENCE_FINAL = 0xffffffff
16
+
17
+ # If this flag set, TxIn#sequence is NOT interpreted as a relative lock-time.
18
+ SEQUENCE_LOCKTIME_DISABLE_FLAG = (1 << 31)
19
+
20
+ # If TxIn#sequence encodes a relative lock-time and this flag is set, the relative lock-time has units of 512 seconds,
21
+ # otherwise it specifies blocks with a granularity of 1.
22
+ SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22)
23
+
24
+ # If TxIn#sequence encodes a relative lock-time, this mask is applied to extract that lock-time from the sequence field.
25
+ SEQUENCE_LOCKTIME_MASK = 0x0000ffff
26
+
27
+ def initialize(out_point: nil, script_sig: Tapyrus::Script.new, script_witness: ScriptWitness.new, sequence: SEQUENCE_FINAL)
28
+ @out_point = out_point
29
+ @script_sig = script_sig
30
+ @script_witness = script_witness
31
+ @sequence = sequence
32
+ end
33
+
34
+ def self.parse_from_payload(payload)
35
+ buf = payload.is_a?(String) ? StringIO.new(payload) : payload
36
+ i = new
37
+ hash, index = buf.read(36).unpack('a32V')
38
+ i.out_point = OutPoint.new(hash.bth, index)
39
+ sig_length = Tapyrus.unpack_var_int_from_io(buf)
40
+ sig = buf.read(sig_length)
41
+ if i.coinbase?
42
+ i.script_sig.chunks[0] = sig
43
+ else
44
+ i.script_sig = Script.parse_from_payload(sig)
45
+ end
46
+ i.sequence = buf.read(4).unpack('V').first
47
+ i
48
+ end
49
+
50
+ def coinbase?
51
+ out_point.coinbase?
52
+ end
53
+
54
+ def to_payload(script_sig = @script_sig, sequence = @sequence, use_malfix: false)
55
+ p = out_point.to_payload
56
+ unless use_malfix
57
+ p << Tapyrus.pack_var_int(script_sig.to_payload.bytesize)
58
+ p << script_sig.to_payload
59
+ end
60
+ p << [sequence].pack('V')
61
+ p
62
+ end
63
+
64
+ def has_witness?
65
+ !script_witness.empty?
66
+ end
67
+
68
+ def to_h
69
+ sig = script_sig.to_h
70
+ sig.delete(:type)
71
+ h = {txid: out_point.txid, vout: out_point.index, script_sig: sig }
72
+ h[:txinwitness] = script_witness.stack.map(&:bth) if has_witness?
73
+ h[:sequence] = sequence
74
+ h
75
+ end
76
+
77
+ def ==(other)
78
+ to_payload == other.to_payload
79
+ end
80
+
81
+ # return previous output hash (not txid)
82
+ def prev_hash
83
+ return nil unless out_point
84
+ out_point.tx_hash
85
+ end
86
+
87
+ end
88
+
89
+ end
@@ -0,0 +1,74 @@
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
+ # transaction output
7
+ class TxOut
8
+
9
+ include OpenAssets::MarkerOutput
10
+
11
+ attr_accessor :value
12
+ attr_accessor :script_pubkey
13
+
14
+ def initialize(value: 0, script_pubkey: nil)
15
+ @value = value
16
+ @script_pubkey = script_pubkey
17
+ end
18
+
19
+ def self.parse_from_payload(payload)
20
+ buf = payload.is_a?(String) ? StringIO.new(payload) : payload
21
+ value = buf.read(8).unpack('q').first
22
+ script_size = Tapyrus.unpack_var_int_from_io(buf)
23
+ new(value: value, script_pubkey: Script.parse_from_payload(buf.read(script_size)))
24
+ end
25
+
26
+ def to_payload
27
+ s = script_pubkey.to_payload
28
+ [value].pack('Q') << Tapyrus.pack_var_int(s.length) << s
29
+ end
30
+
31
+ def to_empty_payload
32
+ 'ffffffffffffffff00'.htb
33
+ end
34
+
35
+ # convert satoshi to btc
36
+ def value_to_btc
37
+ value / 100000000.0
38
+ end
39
+
40
+ def to_h
41
+ {value: value_to_btc, script_pubkey: script_pubkey.to_h}
42
+ end
43
+
44
+ def ==(other)
45
+ to_payload == other.to_payload
46
+ end
47
+
48
+ # Returns this output bytesize
49
+ # @return [Integer] bytesize
50
+ def size
51
+ to_payload.bytesize
52
+ end
53
+
54
+ # Whether this output is dust or not
55
+ # @return [Boolean]
56
+ def dust?
57
+ value < dust_threshold
58
+ end
59
+
60
+ private
61
+
62
+ def dust_threshold
63
+ return 0 if script_pubkey.unspendable?
64
+ n_size = size
65
+ n_size += script_pubkey.witness_program? ? (32 + 4 + 1 + (107 / Tapyrus::WITNESS_SCALE_FACTOR) + 4) : (32 + 4 + 1 + 107 + 4)
66
+ fee = n_size * Tapyrus.chain_params.dust_relay_fee / 1000
67
+ if fee == 0 && n_size != 0
68
+ fee = Tapyrus.chain_params.dust_relay_fee > 0 ? 1 : -1
69
+ end
70
+ fee
71
+ end
72
+ end
73
+
74
+ end
@@ -0,0 +1,133 @@
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 utility.
7
+ # following methods can be used as follows.
8
+ # Tapyrus.pack_var_int(5)
9
+ module Util
10
+
11
+ def pack_var_string(payload)
12
+ pack_var_int(payload.bytesize) + payload
13
+ end
14
+
15
+ def unpack_var_string(payload)
16
+ size, payload = unpack_var_int(payload)
17
+ size > 0 ? payload.unpack("a#{size}a*") : [nil, payload]
18
+ end
19
+
20
+ def pack_var_int(i)
21
+ if i < 0xfd
22
+ [i].pack('C')
23
+ elsif i <= 0xffff
24
+ [0xfd, i].pack('Cv')
25
+ elsif i <= 0xffffffff
26
+ [0xfe, i].pack('CV')
27
+ elsif i <= 0xffffffffffffffff
28
+ [0xff, i].pack('CQ')
29
+ else
30
+ raise "int(#{i}) too large!"
31
+ end
32
+ end
33
+
34
+ # @return an integer for a valid payload, otherwise nil
35
+ def unpack_var_int(payload)
36
+ case payload.unpack('C').first
37
+ when 0xfd
38
+ payload.unpack('xva*')
39
+ when 0xfe
40
+ payload.unpack('xVa*')
41
+ when 0xff
42
+ payload.unpack('xQa*')
43
+ else
44
+ payload.unpack('Ca*')
45
+ end
46
+ end
47
+
48
+ # @return an integer for a valid payload, otherwise nil
49
+ def unpack_var_int_from_io(buf)
50
+ uchar = buf.read(1)&.unpack('C')&.first
51
+ case uchar
52
+ when 0xfd
53
+ buf.read(2)&.unpack('v')&.first
54
+ when 0xfe
55
+ buf.read(4)&.unpack('V')&.first
56
+ when 0xff
57
+ buf.read(8)&.unpack('Q')&.first
58
+ else
59
+ uchar
60
+ end
61
+ end
62
+
63
+ def pack_boolean(b)
64
+ b ? [0x01].pack('C') : [0x00].pack('C')
65
+ end
66
+
67
+ def unpack_boolean(payload)
68
+ data, payload = payload.unpack('Ca*')
69
+ [(data.zero? ? false : true), payload]
70
+ end
71
+
72
+ def sha256(payload)
73
+ Digest::SHA256.digest(payload)
74
+ end
75
+
76
+ def double_sha256(payload)
77
+ sha256(sha256(payload))
78
+ end
79
+
80
+ # byte convert to the sequence of bits packed eight in a byte with the least significant bit first.
81
+ def byte_to_bit(byte)
82
+ byte.unpack('b*').first
83
+ end
84
+
85
+ # padding zero to the left of binary string until bytesize.
86
+ # @param [String] binary string
87
+ # @param [Integer] bytesize total bytesize.
88
+ # @return [String] padded binary string.
89
+ def padding_zero(binary, bytesize)
90
+ return binary unless binary.bytesize < bytesize
91
+ ('00' * (bytesize - binary.bytesize)).htb + binary
92
+ end
93
+
94
+ # generate sha256-ripemd160 hash for value
95
+ def hash160(hex)
96
+ Digest::RMD160.hexdigest(Digest::SHA256.digest(hex.htb))
97
+ end
98
+
99
+ # encode Base58 check address.
100
+ # @param [String] hex the address payload.
101
+ # @param [String] addr_version the address version for P2PKH and P2SH.
102
+ # @return [String] Base58 check encoding address.
103
+ def encode_base58_address(hex, addr_version)
104
+ base = addr_version + hex
105
+ Base58.encode(base + calc_checksum(base))
106
+ end
107
+
108
+ # decode Base58 check encoding address.
109
+ # @param [String] addr address.
110
+ # @return [Array] hex and address version
111
+ def decode_base58_address(addr)
112
+ hex = Base58.decode(addr)
113
+ if hex.size == 50 && calc_checksum(hex[0...-8]) == hex[-8..-1]
114
+ raise 'Invalid version bytes.' unless [Tapyrus.chain_params.address_version, Tapyrus.chain_params.p2sh_version].include?(hex[0..1])
115
+ [hex[2...-8], hex[0..1]]
116
+ else
117
+ raise 'Invalid address.'
118
+ end
119
+ end
120
+
121
+ def calc_checksum(hex)
122
+ double_sha256(hex.htb).bth[0..7]
123
+ end
124
+
125
+ DIGEST_NAME_SHA256 = 'sha256'
126
+
127
+ def hmac_sha256(key, data)
128
+ OpenSSL::HMAC.digest(DIGEST_NAME_SHA256, key, data)
129
+ end
130
+
131
+ end
132
+
133
+ end