tapyrus 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -14
  3. data/exe/tapyrusrbd +2 -2
  4. data/lib/openassets/util.rb +2 -4
  5. data/lib/schnorr.rb +83 -0
  6. data/lib/schnorr/signature.rb +38 -0
  7. data/lib/tapyrus.rb +9 -11
  8. data/lib/tapyrus/block.rb +1 -32
  9. data/lib/tapyrus/block_header.rb +7 -6
  10. data/lib/tapyrus/chain_params.rb +13 -26
  11. data/lib/tapyrus/chainparams/{testnet.yml → dev.yml} +7 -9
  12. data/lib/tapyrus/chainparams/{mainnet.yml → prod.yml} +7 -10
  13. data/lib/tapyrus/constants.rb +12 -34
  14. data/lib/tapyrus/ext.rb +5 -0
  15. data/lib/tapyrus/ext/json_parser.rb +47 -0
  16. data/lib/tapyrus/ext_key.rb +5 -10
  17. data/lib/tapyrus/key.rb +57 -29
  18. data/lib/tapyrus/message.rb +2 -2
  19. data/lib/tapyrus/message/base.rb +1 -0
  20. data/lib/tapyrus/message/block.rb +3 -3
  21. data/lib/tapyrus/message/cmpct_block.rb +3 -5
  22. data/lib/tapyrus/message/tx.rb +2 -2
  23. data/lib/tapyrus/network/peer.rb +1 -15
  24. data/lib/tapyrus/node/cli.rb +15 -11
  25. data/lib/tapyrus/node/configuration.rb +1 -1
  26. data/lib/tapyrus/node/spv.rb +1 -1
  27. data/lib/tapyrus/opcodes.rb +5 -0
  28. data/lib/tapyrus/out_point.rb +1 -1
  29. data/lib/tapyrus/rpc/request_handler.rb +3 -3
  30. data/lib/tapyrus/rpc/tapyrus_core_client.rb +17 -15
  31. data/lib/tapyrus/script/color.rb +79 -0
  32. data/lib/tapyrus/script/multisig.rb +0 -27
  33. data/lib/tapyrus/script/script.rb +74 -89
  34. data/lib/tapyrus/script/script_error.rb +8 -14
  35. data/lib/tapyrus/script/script_interpreter.rb +65 -86
  36. data/lib/tapyrus/script/tx_checker.rb +16 -4
  37. data/lib/tapyrus/secp256k1.rb +1 -0
  38. data/lib/tapyrus/secp256k1/rfc6979.rb +43 -0
  39. data/lib/tapyrus/secp256k1/ruby.rb +5 -31
  40. data/lib/tapyrus/store/chain_entry.rb +1 -0
  41. data/lib/tapyrus/tx.rb +18 -160
  42. data/lib/tapyrus/tx_in.rb +4 -11
  43. data/lib/tapyrus/tx_out.rb +2 -1
  44. data/lib/tapyrus/util.rb +8 -0
  45. data/lib/tapyrus/validation.rb +1 -6
  46. data/lib/tapyrus/version.rb +1 -1
  47. data/lib/tapyrus/wallet/account.rb +1 -0
  48. data/lib/tapyrus/wallet/master_key.rb +1 -0
  49. data/tapyrusrb.gemspec +3 -3
  50. metadata +42 -39
  51. data/lib/tapyrus/chainparams/regtest.yml +0 -38
  52. data/lib/tapyrus/descriptor.rb +0 -147
  53. data/lib/tapyrus/script_witness.rb +0 -38
@@ -3,6 +3,7 @@ module Tapyrus
3
3
 
4
4
  # wrap a block header object with extra data.
5
5
  class ChainEntry
6
+ include Tapyrus::HexConverter
6
7
 
7
8
  attr_reader :header
8
9
  attr_reader :height
@@ -5,18 +5,14 @@ module Tapyrus
5
5
 
6
6
  # Transaction class
7
7
  class Tx
8
+ include Tapyrus::HexConverter
8
9
 
9
10
  MAX_STANDARD_VERSION = 2
10
11
 
11
12
  # The maximum weight for transactions we're willing to relay/mine
12
13
  MAX_STANDARD_TX_WEIGHT = 400000
13
14
 
14
- MARKER = 0x00
15
- FLAG = 0x01
16
-
17
- attr_accessor :version
18
- attr_accessor :marker
19
- attr_accessor :flag
15
+ attr_accessor :features
20
16
  attr_reader :inputs
21
17
  attr_reader :outputs
22
18
  attr_accessor :lock_time
@@ -24,30 +20,19 @@ module Tapyrus
24
20
  def initialize
25
21
  @inputs = []
26
22
  @outputs = []
27
- @version = 1
23
+ @features = 1
28
24
  @lock_time = 0
29
25
  end
30
26
 
31
27
  alias_method :in, :inputs
32
28
  alias_method :out, :outputs
33
29
 
34
- def self.parse_from_payload(payload, non_witness: false)
30
+ def self.parse_from_payload(payload)
35
31
  buf = payload.is_a?(String) ? StringIO.new(payload) : payload
36
32
  tx = new
37
- tx.version = buf.read(4).unpack('V').first
33
+ tx.features = buf.read(4).unpack('V').first
38
34
 
39
35
  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
36
 
52
37
  in_count.times do
53
38
  tx.inputs << TxIn.parse_from_payload(buf)
@@ -58,101 +43,46 @@ module Tapyrus
58
43
  tx.outputs << TxOut.parse_from_payload(buf)
59
44
  end
60
45
 
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
46
  tx.lock_time = buf.read(4).unpack('V').first
68
47
 
69
48
  tx
70
49
  end
71
50
 
72
51
  def hash
73
- to_payload.bth.to_i(16)
52
+ to_hex.to_i(16)
74
53
  end
75
54
 
76
55
  def tx_hash
77
- Tapyrus.double_sha256(serialize_old_format).bth
56
+ Tapyrus.double_sha256(to_payload).bth
78
57
  end
79
58
 
80
59
  def txid
81
- buf = [version].pack('V')
60
+ buf = [features].pack('V')
82
61
  buf << Tapyrus.pack_var_int(inputs.length) << inputs.map{|i|i.to_payload(use_malfix: true)}.join
83
62
  buf << Tapyrus.pack_var_int(outputs.length) << outputs.map(&:to_payload).join
84
63
  buf << [lock_time].pack('V')
85
64
  Tapyrus.double_sha256(buf).reverse.bth
86
65
  end
87
66
 
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
67
  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')
68
+ buf = [features].pack('V')
132
69
  buf << Tapyrus.pack_var_int(inputs.length) << inputs.map(&:to_payload).join
133
70
  buf << Tapyrus.pack_var_int(outputs.length) << outputs.map(&:to_payload).join
134
71
  buf << [lock_time].pack('V')
135
72
  buf
136
73
  end
137
74
 
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
75
+ def coinbase_tx?
76
+ inputs.length == 1 && inputs.first.coinbase?
146
77
  end
147
78
 
148
- def witness_payload
149
- inputs.map { |i| i.script_witness.to_payload }.join
79
+ def ==(other)
80
+ to_payload == other.to_payload
150
81
  end
151
82
 
152
83
  # check this tx is standard.
153
84
  def standard?
154
- return false if version > MAX_STANDARD_VERSION
155
- return false if weight > MAX_STANDARD_TX_WEIGHT
85
+ return false if features > MAX_STANDARD_VERSION
156
86
  inputs.each do |i|
157
87
  # Biggest 'standard' txin is a 15-of-15 P2SH multisig with compressed keys (remember the 520 byte limit on redeemScript size).
158
88
  # That works out to a (15*(33+1))+3=513 byte redeemScript, 513+1+15*(73+1)+3=1627
@@ -177,21 +107,6 @@ module Tapyrus
177
107
  to_payload.bytesize
178
108
  end
179
109
 
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
110
  # get signature hash
196
111
  # @param [Integer] input_index input index.
197
112
  # @param [Integer] hash_type signature hash type
@@ -205,13 +120,7 @@ module Tapyrus
205
120
  raise ArgumentError, 'does not exist input corresponding to input_index.' if input_index >= inputs.size
206
121
  raise ArgumentError, 'script_pubkey must be specified.' unless output_script
207
122
  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
123
+ sighash_for_legacy(input_index, output_script, hash_type)
215
124
  end
216
125
 
217
126
  # verify input signature.
@@ -220,25 +129,15 @@ module Tapyrus
220
129
  # @param [Integer] amount the amount of tapyrus, require for witness program only.
221
130
  # @param [Array] flags the flags used when execute script interpreter.
222
131
  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
132
  if script_pubkey.p2sh?
227
133
  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
134
  end
135
+ verify_input_sig_for_legacy(input_index, script_pubkey, flags)
237
136
  end
238
137
 
239
138
  def to_h
240
139
  {
241
- txid: txid, hash: witness_hash.rhex, version: version, size: size, vsize: vsize, locktime: lock_time,
140
+ txid: txid, hash: tx_hash, features: features, size: size, locktime: lock_time,
242
141
  vin: inputs.map(&:to_h), vout: outputs.map.with_index{|tx_out, index| tx_out.to_h.merge({n: index})}
243
142
  }
244
143
  end
@@ -285,40 +184,12 @@ module Tapyrus
285
184
  ins = [ins[index]]
286
185
  end
287
186
 
288
- buf = [[version].pack('V'), Tapyrus.pack_var_int(ins.size),
187
+ buf = [[features].pack('V'), Tapyrus.pack_var_int(ins.size),
289
188
  ins, out_size, outs, [lock_time, hash_type].pack('VV')].join
290
189
 
291
190
  Tapyrus.double_sha256(buf)
292
191
  end
293
192
 
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
193
 
323
194
  # verify input signature for legacy tx.
324
195
  def verify_input_sig_for_legacy(input_index, script_pubkey, flags)
@@ -329,19 +200,6 @@ module Tapyrus
329
200
  interpreter.verify_script(script_sig, script_pubkey)
330
201
  end
331
202
 
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
203
  end
346
204
 
347
205
  end
@@ -9,7 +9,6 @@ module Tapyrus
9
9
  attr_accessor :out_point
10
10
  attr_accessor :script_sig
11
11
  attr_accessor :sequence
12
- attr_accessor :script_witness
13
12
 
14
13
  # Setting nSequence to this value for every input in a transaction disables nLockTime.
15
14
  SEQUENCE_FINAL = 0xffffffff
@@ -24,10 +23,9 @@ module Tapyrus
24
23
  # If TxIn#sequence encodes a relative lock-time, this mask is applied to extract that lock-time from the sequence field.
25
24
  SEQUENCE_LOCKTIME_MASK = 0x0000ffff
26
25
 
27
- def initialize(out_point: nil, script_sig: Tapyrus::Script.new, script_witness: ScriptWitness.new, sequence: SEQUENCE_FINAL)
26
+ def initialize(out_point: nil, script_sig: Tapyrus::Script.new, sequence: SEQUENCE_FINAL)
28
27
  @out_point = out_point
29
28
  @script_sig = script_sig
30
- @script_witness = script_witness
31
29
  @sequence = sequence
32
30
  end
33
31
 
@@ -37,11 +35,10 @@ module Tapyrus
37
35
  hash, index = buf.read(36).unpack('a32V')
38
36
  i.out_point = OutPoint.new(hash.bth, index)
39
37
  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
38
+ if sig_length == 0
39
+ i.script_sig = Script.new
43
40
  else
44
- i.script_sig = Script.parse_from_payload(sig)
41
+ i.script_sig = Script.parse_from_payload(buf.read(sig_length))
45
42
  end
46
43
  i.sequence = buf.read(4).unpack('V').first
47
44
  i
@@ -61,15 +58,11 @@ module Tapyrus
61
58
  p
62
59
  end
63
60
 
64
- def has_witness?
65
- !script_witness.empty?
66
- end
67
61
 
68
62
  def to_h
69
63
  sig = script_sig.to_h
70
64
  sig.delete(:type)
71
65
  h = {txid: out_point.txid, vout: out_point.index, script_sig: sig }
72
- h[:txinwitness] = script_witness.stack.map(&:bth) if has_witness?
73
66
  h[:sequence] = sequence
74
67
  h
75
68
  end
@@ -7,6 +7,7 @@ module Tapyrus
7
7
  class TxOut
8
8
 
9
9
  include OpenAssets::MarkerOutput
10
+ include Tapyrus::Color::ColoredOutput
10
11
 
11
12
  attr_accessor :value
12
13
  attr_accessor :script_pubkey
@@ -62,7 +63,7 @@ module Tapyrus
62
63
  def dust_threshold
63
64
  return 0 if script_pubkey.unspendable?
64
65
  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
+ n_size += (32 + 4 + 1 + 107 + 4)
66
67
  fee = n_size * Tapyrus.chain_params.dust_relay_fee / 1000
67
68
  if fee == 0 && n_size != 0
68
69
  fee = Tapyrus.chain_params.dust_relay_fee > 0 ? 1 : -1
@@ -130,4 +130,12 @@ module Tapyrus
130
130
 
131
131
  end
132
132
 
133
+ module HexConverter
134
+
135
+ def to_hex
136
+ to_payload.bth
137
+ end
138
+
139
+ end
140
+
133
141
  end
@@ -13,11 +13,6 @@ module Tapyrus
13
13
  return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason: 'bad-txns-vout-empty')
14
14
  end
15
15
 
16
- # Size limits (this doesn't take the witness into account, as that hasn't been checked for malleability)
17
- if tx.serialize_old_format.bytesize * Tapyrus::WITNESS_SCALE_FACTOR > Tapyrus::MAX_BLOCK_WEIGHT
18
- return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason: 'bad-txns-oversize')
19
- end
20
-
21
16
  # Check for negative or overflow output values
22
17
  amount = 0
23
18
  tx.outputs.each do |o|
@@ -34,7 +29,7 @@ module Tapyrus
34
29
  end
35
30
 
36
31
  if tx.coinbase_tx?
37
- if tx.inputs[0].script_sig.size < 2 || tx.inputs[0].script_sig.size > 100
32
+ if tx.inputs[0].out_point.index == 0xffffffff || tx.inputs[0].script_sig.size > 100
38
33
  return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason: 'bad-cb-length')
39
34
  end
40
35
  else
@@ -1,3 +1,3 @@
1
1
  module Tapyrus
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -3,6 +3,7 @@ module Tapyrus
3
3
 
4
4
  # the account in BIP-44
5
5
  class Account
6
+ include Tapyrus::HexConverter
6
7
 
7
8
  PURPOSE_TYPE = {legacy: 44, nested_witness: 49, native_segwit: 84}
8
9
 
@@ -3,6 +3,7 @@ module Tapyrus
3
3
 
4
4
  # HD Wallet master seed
5
5
  class MasterKey
6
+ include Tapyrus::HexConverter
6
7
  extend Tapyrus::Util
7
8
  include Tapyrus::Util
8
9
  include Tapyrus::KeyPath
@@ -23,25 +23,25 @@ Gem::Specification.new do |spec|
23
23
  spec.add_runtime_dependency 'ecdsa'
24
24
  spec.add_runtime_dependency 'eventmachine'
25
25
  spec.add_runtime_dependency 'murmurhash3'
26
- spec.add_runtime_dependency 'bech32', '~> 1.0.3'
27
26
  spec.add_runtime_dependency 'daemon-spawn'
28
27
  spec.add_runtime_dependency 'thor'
29
28
  spec.add_runtime_dependency 'ffi'
30
29
  spec.add_runtime_dependency 'leb128', '~> 1.0.0'
31
30
  spec.add_runtime_dependency 'eventmachine_httpserver'
32
- spec.add_runtime_dependency 'rest-client'
33
31
  spec.add_runtime_dependency 'iniparse'
34
32
  spec.add_runtime_dependency 'siphash'
35
33
  spec.add_runtime_dependency 'protobuf', '3.8.5'
36
34
  spec.add_runtime_dependency 'scrypt'
37
35
  spec.add_runtime_dependency 'activesupport', '>= 5.2.3'
36
+ spec.add_runtime_dependency 'json_pure', '>= 2.3.1'
38
37
 
39
38
  # for options
40
39
  spec.add_development_dependency 'leveldb-native'
41
40
 
42
41
  spec.add_development_dependency 'bundler'
43
- spec.add_development_dependency 'rake', '~> 10.0'
42
+ spec.add_development_dependency 'rake', '>= 12.3.3'
44
43
  spec.add_development_dependency 'rspec', '~> 3.0'
45
44
  spec.add_development_dependency 'timecop'
45
+ spec.add_development_dependency 'webmock', '~> 3.0'
46
46
 
47
47
  end