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
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