tapyrus 0.2.7 → 0.2.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +37 -0
  3. data/.prettierignore +3 -0
  4. data/.prettierrc.yaml +3 -0
  5. data/.ruby-version +1 -1
  6. data/CODE_OF_CONDUCT.md +7 -7
  7. data/README.md +14 -17
  8. data/Rakefile +3 -3
  9. data/lib/openassets/marker_output.rb +0 -4
  10. data/lib/openassets/payload.rb +4 -10
  11. data/lib/openassets.rb +0 -2
  12. data/lib/schnorr/sign_to_contract.rb +51 -0
  13. data/lib/schnorr/signature.rb +3 -6
  14. data/lib/schnorr.rb +14 -9
  15. data/lib/tapyrus/base58.rb +7 -6
  16. data/lib/tapyrus/bip175.rb +78 -0
  17. data/lib/tapyrus/block.rb +1 -2
  18. data/lib/tapyrus/block_header.rb +15 -9
  19. data/lib/tapyrus/bloom_filter.rb +5 -3
  20. data/lib/tapyrus/chain_params.rb +1 -4
  21. data/lib/tapyrus/chainparams/dev.yml +3 -2
  22. data/lib/tapyrus/chainparams/prod.yml +3 -2
  23. data/lib/tapyrus/constants.rb +29 -23
  24. data/lib/tapyrus/errors.rb +1 -3
  25. data/lib/tapyrus/ext/ecdsa.rb +4 -4
  26. data/lib/tapyrus/ext/json_parser.rb +1 -4
  27. data/lib/tapyrus/ext.rb +1 -1
  28. data/lib/tapyrus/ext_key.rb +44 -32
  29. data/lib/tapyrus/key.rb +31 -35
  30. data/lib/tapyrus/key_path.rb +15 -12
  31. data/lib/tapyrus/logger.rb +20 -16
  32. data/lib/tapyrus/merkle_tree.rb +22 -20
  33. data/lib/tapyrus/message/addr.rb +1 -7
  34. data/lib/tapyrus/message/base.rb +0 -3
  35. data/lib/tapyrus/message/block.rb +2 -9
  36. data/lib/tapyrus/message/block_transaction_request.rb +3 -6
  37. data/lib/tapyrus/message/block_transactions.rb +2 -6
  38. data/lib/tapyrus/message/block_txn.rb +0 -4
  39. data/lib/tapyrus/message/cmpct_block.rb +1 -7
  40. data/lib/tapyrus/message/error.rb +1 -4
  41. data/lib/tapyrus/message/fee_filter.rb +1 -4
  42. data/lib/tapyrus/message/filter_add.rb +0 -4
  43. data/lib/tapyrus/message/filter_clear.rb +0 -4
  44. data/lib/tapyrus/message/filter_load.rb +2 -5
  45. data/lib/tapyrus/message/get_addr.rb +0 -4
  46. data/lib/tapyrus/message/get_block_txn.rb +0 -4
  47. data/lib/tapyrus/message/get_blocks.rb +0 -3
  48. data/lib/tapyrus/message/get_data.rb +1 -4
  49. data/lib/tapyrus/message/get_headers.rb +1 -3
  50. data/lib/tapyrus/message/header_and_short_ids.rb +3 -9
  51. data/lib/tapyrus/message/headers.rb +0 -4
  52. data/lib/tapyrus/message/headers_parser.rb +3 -8
  53. data/lib/tapyrus/message/inv.rb +1 -4
  54. data/lib/tapyrus/message/inventories_parser.rb +2 -7
  55. data/lib/tapyrus/message/inventory.rb +12 -5
  56. data/lib/tapyrus/message/mem_pool.rb +0 -4
  57. data/lib/tapyrus/message/merkle_block.rb +4 -9
  58. data/lib/tapyrus/message/network_addr.rb +7 -6
  59. data/lib/tapyrus/message/not_found.rb +0 -3
  60. data/lib/tapyrus/message/ping.rb +0 -3
  61. data/lib/tapyrus/message/pong.rb +0 -3
  62. data/lib/tapyrus/message/prefilled_tx.rb +0 -4
  63. data/lib/tapyrus/message/reject.rb +0 -3
  64. data/lib/tapyrus/message/send_cmpct.rb +1 -3
  65. data/lib/tapyrus/message/send_headers.rb +0 -3
  66. data/lib/tapyrus/message/tx.rb +0 -4
  67. data/lib/tapyrus/message/ver_ack.rb +1 -5
  68. data/lib/tapyrus/message/version.rb +2 -5
  69. data/lib/tapyrus/message.rb +14 -16
  70. data/lib/tapyrus/mnemonic.rb +17 -15
  71. data/lib/tapyrus/network/connection.rb +0 -3
  72. data/lib/tapyrus/network/message_handler.rb +61 -60
  73. data/lib/tapyrus/network/peer.rb +13 -12
  74. data/lib/tapyrus/network/peer_discovery.rb +10 -9
  75. data/lib/tapyrus/network/pool.rb +12 -12
  76. data/lib/tapyrus/network.rb +0 -2
  77. data/lib/tapyrus/node/cli.rb +12 -14
  78. data/lib/tapyrus/node/configuration.rb +1 -3
  79. data/lib/tapyrus/node/spv.rb +2 -3
  80. data/lib/tapyrus/node.rb +1 -1
  81. data/lib/tapyrus/opcodes.rb +9 -7
  82. data/lib/tapyrus/out_point.rb +5 -5
  83. data/lib/tapyrus/rpc/http_server.rb +21 -22
  84. data/lib/tapyrus/rpc/request_handler.rb +16 -21
  85. data/lib/tapyrus/rpc/tapyrus_core_client.rb +67 -25
  86. data/lib/tapyrus/rpc.rb +1 -0
  87. data/lib/tapyrus/script/color.rb +10 -0
  88. data/lib/tapyrus/script/multisig.rb +13 -12
  89. data/lib/tapyrus/script/script.rb +93 -88
  90. data/lib/tapyrus/script/script_error.rb +1 -4
  91. data/lib/tapyrus/script/script_interpreter.rb +439 -399
  92. data/lib/tapyrus/script/tx_checker.rb +20 -10
  93. data/lib/tapyrus/secp256k1/native.rb +14 -15
  94. data/lib/tapyrus/secp256k1/rfc6979.rb +7 -4
  95. data/lib/tapyrus/secp256k1/ruby.rb +10 -12
  96. data/lib/tapyrus/secp256k1.rb +0 -4
  97. data/lib/tapyrus/slip39/share.rb +41 -29
  98. data/lib/tapyrus/slip39/sss.rb +92 -49
  99. data/lib/tapyrus/slip39.rb +20 -5
  100. data/lib/tapyrus/store/chain_entry.rb +0 -4
  101. data/lib/tapyrus/store/db/level_db.rb +5 -9
  102. data/lib/tapyrus/store/db.rb +0 -2
  103. data/lib/tapyrus/store/spv_chain.rb +11 -17
  104. data/lib/tapyrus/store.rb +1 -3
  105. data/lib/tapyrus/tx.rb +45 -37
  106. data/lib/tapyrus/tx_builder.rb +160 -0
  107. data/lib/tapyrus/tx_in.rb +1 -6
  108. data/lib/tapyrus/tx_out.rb +2 -7
  109. data/lib/tapyrus/util.rb +7 -9
  110. data/lib/tapyrus/validation.rb +12 -11
  111. data/lib/tapyrus/version.rb +1 -1
  112. data/lib/tapyrus/wallet/account.rb +22 -18
  113. data/lib/tapyrus/wallet/base.rb +12 -9
  114. data/lib/tapyrus/wallet/db.rb +6 -9
  115. data/lib/tapyrus/wallet/master_key.rb +2 -4
  116. data/lib/tapyrus.rb +7 -22
  117. data/tapyrusrb.gemspec +13 -14
  118. metadata +26 -7
  119. data/.travis.yml +0 -14
@@ -1,6 +1,5 @@
1
1
  module Tapyrus
2
2
  module SLIP39
3
-
4
3
  WORDS = File.readlines("#{__dir__}/slip39/wordlist/english.txt").map(&:strip)
5
4
 
6
5
  module_function
@@ -15,37 +14,53 @@ module Tapyrus
15
14
 
16
15
  # The length of the radix in bits.
17
16
  RADIX_BITS = 10
17
+
18
18
  # The number of words in the wordlist.
19
- RADIX = 2 ** RADIX_BITS
19
+ RADIX = 2**RADIX_BITS
20
+
20
21
  # The length of the random identifier in bits.
21
22
  ID_LENGTH_BITS = 15
23
+
22
24
  # The length of the iteration exponent in bits.
23
25
  ITERATION_EXP_LENGTH_BITS = 5
26
+
24
27
  # The length of the random identifier and iteration exponent in words.
25
28
  ID_EXP_LENGTH_WORDS = bits_to_words(ID_LENGTH_BITS + ITERATION_EXP_LENGTH_BITS)
29
+
26
30
  # The maximum number of shares that can be created.
27
31
  MAX_SHARE_COUNT = 16
32
+
28
33
  # The length of the RS1024 checksum in words.
29
34
  CHECKSUM_LENGTH_WORDS = 3
35
+
30
36
  # The length of the digest of the shared secret in bytes.
31
37
  DIGEST_LENGTH_BYTES = 4
38
+
32
39
  # The customization string used in the RS1024 checksum and in the PBKDF2 salt.
33
40
  CUSTOMIZATION_STRING = 'shamir'.bytes
41
+
34
42
  # The length of the mnemonic in words without the share value.
35
43
  METADATA_LENGTH_WORDS = ID_EXP_LENGTH_WORDS + 2 + CHECKSUM_LENGTH_WORDS
44
+
36
45
  # The minimum allowed entropy of the master secret.
37
46
  MIN_STRENGTH_BITS = 128
47
+
38
48
  # The minimum allowed length of the mnemonic in words.
39
49
  MIN_MNEMONIC_LENGTH_WORDS = METADATA_LENGTH_WORDS + bits_to_words(MIN_STRENGTH_BITS)
50
+
40
51
  # The minimum number of iterations to use in PBKDF2.
41
- BASE_ITERATION_COUNT = 10000
52
+ BASE_ITERATION_COUNT = 10_000
53
+
42
54
  # The number of rounds to use in the Feistel cipher.
43
55
  ROUND_COUNT = 4
56
+
44
57
  # The index of the share containing the shared secret.
45
58
  SECRET_INDEX = 255
59
+
46
60
  # The index of the share containing the digest of the shared secret.
47
61
  DIGEST_INDEX = 254
48
62
 
63
+ # prettier-ignore
49
64
  EXP_TABLE = [
50
65
  1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19,
51
66
  53, 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34,
@@ -66,6 +81,7 @@ module Tapyrus
66
81
  57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246
67
82
  ]
68
83
 
84
+ # prettier-ignore
69
85
  LOG_TABLE = [
70
86
  0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3,
71
87
  100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28,
@@ -88,6 +104,5 @@ module Tapyrus
88
104
 
89
105
  autoload :SSS, 'tapyrus/slip39/sss'
90
106
  autoload :Share, 'tapyrus/slip39/share'
91
-
92
107
  end
93
- end
108
+ end
@@ -1,6 +1,5 @@
1
1
  module Tapyrus
2
2
  module Store
3
-
4
3
  # wrap a block header object with extra data.
5
4
  class ChainEntry
6
5
  include Tapyrus::HexConverter
@@ -60,9 +59,6 @@ module Tapyrus
60
59
  height_value = height_value.htb.reverse
61
60
  Tapyrus.pack_var_int(height_value.bytesize) + height_value + header.to_payload
62
61
  end
63
-
64
62
  end
65
-
66
63
  end
67
-
68
64
  end
@@ -3,9 +3,7 @@ require 'leveldb-native'
3
3
  module Tapyrus
4
4
  module Store
5
5
  module DB
6
-
7
6
  class LevelDB
8
-
9
7
  attr_reader :db
10
8
  attr_reader :logger
11
9
 
@@ -62,9 +60,11 @@ module Tapyrus
62
60
  # @param [Tapyrus::Store::ChainEntry]
63
61
  def save_entry(entry)
64
62
  db.batch do
65
- db.put(entry.key ,entry.to_payload)
63
+ db.put(entry.key, entry.to_payload)
66
64
  db.put(height_key(entry.height), entry.block_hash)
67
- add_agg_pubkey(entry.height == 0 ? 0 : entry.height + 1, entry.header.x_field) if entry.header.upgrade_agg_pubkey?
65
+ if entry.header.upgrade_agg_pubkey?
66
+ add_agg_pubkey(entry.height == 0 ? 0 : entry.height + 1, entry.header.x_field)
67
+ end
68
68
  connect_entry(entry)
69
69
  end
70
70
  end
@@ -134,9 +134,7 @@ module Tapyrus
134
134
  unless tip_block.block_hash == entry.prev_hash
135
135
  raise "entry(#{entry.block_hash}) does not reference current best block hash(#{tip_block.block_hash})"
136
136
  end
137
- unless tip_block.height + 1 == entry.height
138
- raise "block height is small than current best block."
139
- end
137
+ raise 'block height is small than current best block.' unless tip_block.height + 1 == entry.height
140
138
  end
141
139
  db.put(KEY_PREFIX[:best], entry.block_hash)
142
140
  db.put(KEY_PREFIX[:next] + entry.prev_hash, entry.block_hash)
@@ -148,9 +146,7 @@ module Tapyrus
148
146
  index = db.get(KEY_PREFIX[:latest_agg_pubkey])
149
147
  index&.to_i(16)
150
148
  end
151
-
152
149
  end
153
-
154
150
  end
155
151
  end
156
152
  end
@@ -1,9 +1,7 @@
1
1
  module Tapyrus
2
2
  module Store
3
-
4
3
  module DB
5
4
  autoload :LevelDB, 'tapyrus/store/db/level_db'
6
5
  end
7
-
8
6
  end
9
7
  end
@@ -1,18 +1,15 @@
1
1
  module Tapyrus
2
-
3
2
  module Store
4
-
5
3
  KEY_PREFIX = {
6
- entry: 'e', # key: block hash, value: Tapyrus::Store::ChainEntry payload
7
- height: 'h', # key: block height, value: block hash.
8
- best: 'B', # value: best block hash.
9
- next: 'n', # key: block hash, value: A hash of the next block of the specified hash
10
- agg_pubkey: 'a', # key: index, value: Activated block height | aggregated public key.
11
- latest_agg_pubkey: 'g' # value: latest agg pubkey index.
4
+ entry: 'e', # key: block hash, value: Tapyrus::Store::ChainEntry payload
5
+ height: 'h', # key: block height, value: block hash.
6
+ best: 'B', # value: best block hash.
7
+ next: 'n', # key: block hash, value: A hash of the next block of the specified hash
8
+ agg_pubkey: 'a', # key: index, value: Activated block height | aggregated public key.
9
+ latest_agg_pubkey: 'g' # value: latest agg pubkey index.
12
10
  }
13
11
 
14
12
  class SPVChain
15
-
16
13
  attr_reader :db
17
14
  attr_reader :logger
18
15
 
@@ -53,7 +50,9 @@ module Tapyrus
53
50
  logger.info("append header #{header.block_id}")
54
51
  best_block = latest_block
55
52
  current_height = best_block.height
56
- raise "this header is invalid. #{header.block_hash}" unless header.valid?(db.agg_pubkey_with_height(current_height + 1))
53
+ unless header.valid?(db.agg_pubkey_with_height(current_height + 1))
54
+ raise "this header is invalid. #{header.block_hash}"
55
+ end
57
56
  if best_block.block_hash == header.prev_hash
58
57
  entry = Tapyrus::Store::ChainEntry.new(header, current_height + 1)
59
58
  db.save_entry(entry)
@@ -107,13 +106,8 @@ module Tapyrus
107
106
  # if database is empty, put genesis block.
108
107
  # @param [Tapyrus::Block] genesis genesis block
109
108
  def initialize_block(genesis)
110
- unless latest_block
111
- db.save_entry(ChainEntry.new(genesis.header, 0))
112
- end
109
+ db.save_entry(ChainEntry.new(genesis.header, 0)) unless latest_block
113
110
  end
114
-
115
111
  end
116
-
117
112
  end
118
-
119
- end
113
+ end
data/lib/tapyrus/store.rb CHANGED
@@ -1,9 +1,7 @@
1
1
  module Tapyrus
2
2
  module Store
3
-
4
3
  autoload :DB, 'tapyrus/store/db'
5
4
  autoload :SPVChain, 'tapyrus/store/spv_chain'
6
5
  autoload :ChainEntry, 'tapyrus/store/chain_entry'
7
-
8
6
  end
9
- end
7
+ end
data/lib/tapyrus/tx.rb CHANGED
@@ -2,7 +2,6 @@
2
2
  # https://github.com/lian/bitcoin-ruby/blob/master/COPYING
3
3
 
4
4
  module Tapyrus
5
-
6
5
  # Transaction class
7
6
  class Tx
8
7
  include Tapyrus::HexConverter
@@ -10,7 +9,7 @@ module Tapyrus
10
9
  MAX_STANDARD_VERSION = 2
11
10
 
12
11
  # The maximum weight for transactions we're willing to relay/mine
13
- MAX_STANDARD_TX_WEIGHT = 400000
12
+ MAX_STANDARD_TX_WEIGHT = 400_000
14
13
 
15
14
  attr_accessor :features
16
15
  attr_reader :inputs
@@ -34,14 +33,10 @@ module Tapyrus
34
33
 
35
34
  in_count = Tapyrus.unpack_var_int_from_io(buf)
36
35
 
37
- in_count.times do
38
- tx.inputs << TxIn.parse_from_payload(buf)
39
- end
36
+ in_count.times { tx.inputs << TxIn.parse_from_payload(buf) }
40
37
 
41
38
  out_count = Tapyrus.unpack_var_int_from_io(buf)
42
- out_count.times do
43
- tx.outputs << TxOut.parse_from_payload(buf)
44
- end
39
+ out_count.times { tx.outputs << TxOut.parse_from_payload(buf) }
45
40
 
46
41
  tx.lock_time = buf.read(4).unpack('V').first
47
42
 
@@ -58,7 +53,7 @@ module Tapyrus
58
53
 
59
54
  def txid
60
55
  buf = [features].pack('V')
61
- buf << Tapyrus.pack_var_int(inputs.length) << inputs.map{|i|i.to_payload(use_malfix: true)}.join
56
+ buf << Tapyrus.pack_var_int(inputs.length) << inputs.map { |i| i.to_payload(use_malfix: true) }.join
62
57
  buf << Tapyrus.pack_var_int(outputs.length) << outputs.map(&:to_payload).join
63
58
  buf << [lock_time].pack('V')
64
59
  Tapyrus.double_sha256(buf).reverse.bth
@@ -95,6 +90,7 @@ module Tapyrus
95
90
  outputs.each do |o|
96
91
  return false unless o.script_pubkey.standard?
97
92
  data_count += 1 if o.script_pubkey.op_return?
93
+
98
94
  # TODO add non P2SH multisig relay(permitbaremultisig)
99
95
  return false if o.dust?
100
96
  end
@@ -114,8 +110,14 @@ module Tapyrus
114
110
  # @param [Integer] amount tapyrus amount locked in input. required for witness input only.
115
111
  # @param [Integer] skip_separator_index If output_script is P2WSH and output_script contains any OP_CODESEPARATOR,
116
112
  # 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.
117
- def sighash_for_input(input_index, output_script, hash_type: SIGHASH_TYPE[:all],
118
- sig_version: :base, amount: nil, skip_separator_index: 0)
113
+ def sighash_for_input(
114
+ input_index,
115
+ output_script,
116
+ hash_type: SIGHASH_TYPE[:all],
117
+ sig_version: :base,
118
+ amount: nil,
119
+ skip_separator_index: 0
120
+ )
119
121
  raise ArgumentError, 'input_index must be specified.' unless input_index
120
122
  raise ArgumentError, 'does not exist input corresponding to input_index.' if input_index >= inputs.size
121
123
  raise ArgumentError, 'script_pubkey must be specified.' unless output_script
@@ -129,16 +131,19 @@ module Tapyrus
129
131
  # @param [Integer] amount the amount of tapyrus, require for witness program only.
130
132
  # @param [Array] flags the flags used when execute script interpreter.
131
133
  def verify_input_sig(input_index, script_pubkey, amount: nil, flags: STANDARD_SCRIPT_VERIFY_FLAGS)
132
- if script_pubkey.p2sh?
133
- flags << SCRIPT_VERIFY_P2SH
134
- end
134
+ flags << SCRIPT_VERIFY_P2SH if script_pubkey.p2sh?
135
135
  verify_input_sig_for_legacy(input_index, script_pubkey, flags)
136
136
  end
137
137
 
138
138
  def to_h
139
139
  {
140
- txid: txid, hash: tx_hash, features: features, size: size, locktime: lock_time,
141
- vin: inputs.map(&:to_h), vout: outputs.map.with_index{|tx_out, index| tx_out.to_h.merge({n: index})}
140
+ txid: txid,
141
+ hash: tx_hash,
142
+ features: features,
143
+ size: size,
144
+ locktime: lock_time,
145
+ vin: inputs.map(&:to_h),
146
+ vout: outputs.map.with_index { |tx_out, index| tx_out.to_h.merge({ n: index }) }
142
147
  }
143
148
  end
144
149
 
@@ -154,43 +159,48 @@ module Tapyrus
154
159
 
155
160
  # generate sighash with legacy format
156
161
  def sighash_for_legacy(index, script_code, hash_type)
157
- ins = inputs.map.with_index do |i, idx|
158
- if idx == index
159
- i.to_payload(script_code.delete_opcode(Tapyrus::Opcodes::OP_CODESEPARATOR))
160
- else
161
- case hash_type & 0x1f
162
+ ins =
163
+ inputs.map.with_index do |i, idx|
164
+ if idx == index
165
+ i.to_payload(script_code.delete_opcode(Tapyrus::Opcodes::OP_CODESEPARATOR))
166
+ else
167
+ case hash_type & 0x1f
162
168
  when SIGHASH_TYPE[:none], SIGHASH_TYPE[:single]
163
169
  i.to_payload(Tapyrus::Script.new, 0)
164
170
  else
165
171
  i.to_payload(Tapyrus::Script.new)
172
+ end
166
173
  end
167
174
  end
168
- end
169
175
 
170
176
  outs = outputs.map(&:to_payload)
171
177
  out_size = Tapyrus.pack_var_int(outputs.size)
172
178
 
173
179
  case hash_type & 0x1f
174
- when SIGHASH_TYPE[:none]
175
- outs = ''
176
- out_size = Tapyrus.pack_var_int(0)
177
- when SIGHASH_TYPE[:single]
178
- return "\x01".ljust(32, "\x00") if index >= outputs.size
179
- outs = outputs[0...(index + 1)].map.with_index { |o, idx| (idx == index) ? o.to_payload : o.to_empty_payload }.join
180
- out_size = Tapyrus.pack_var_int(index + 1)
180
+ when SIGHASH_TYPE[:none]
181
+ outs = ''
182
+ out_size = Tapyrus.pack_var_int(0)
183
+ when SIGHASH_TYPE[:single]
184
+ return "\x01".ljust(32, "\x00") if index >= outputs.size
185
+ outs =
186
+ outputs[0...(index + 1)].map.with_index { |o, idx| (idx == index) ? o.to_payload : o.to_empty_payload }.join
187
+ out_size = Tapyrus.pack_var_int(index + 1)
181
188
  end
182
189
 
183
- if hash_type & SIGHASH_TYPE[:anyonecanpay] != 0
184
- ins = [ins[index]]
185
- end
190
+ ins = [ins[index]] if hash_type & SIGHASH_TYPE[:anyonecanpay] != 0
186
191
 
187
- buf = [[features].pack('V'), Tapyrus.pack_var_int(ins.size),
188
- ins, out_size, outs, [lock_time, hash_type].pack('VV')].join
192
+ buf = [
193
+ [features].pack('V'),
194
+ Tapyrus.pack_var_int(ins.size),
195
+ ins,
196
+ out_size,
197
+ outs,
198
+ [lock_time, hash_type].pack('VV')
199
+ ].join
189
200
 
190
201
  Tapyrus.double_sha256(buf)
191
202
  end
192
203
 
193
-
194
204
  # verify input signature for legacy tx.
195
205
  def verify_input_sig_for_legacy(input_index, script_pubkey, flags)
196
206
  script_sig = inputs[input_index].script_sig
@@ -199,7 +209,5 @@ module Tapyrus
199
209
 
200
210
  interpreter.verify_script(script_sig, script_pubkey)
201
211
  end
202
-
203
212
  end
204
-
205
213
  end
@@ -0,0 +1,160 @@
1
+ module Tapyrus
2
+ #
3
+ # Transaction Builder class.
4
+ #
5
+ # TxBuilder makes it easy to build transactions without having to deal with TxOut/TxIn/Script directly.
6
+ #
7
+ # @example
8
+ #
9
+ # txb = Tapyrus::TxBuilder.new
10
+ # utxo1 = {
11
+ # script_pubkey: Tapyrus::Script.parse_from_addr('mgCuyNQ1pUbKqL57tJQZX3hhUCaZcuX3RQ'),
12
+ # txid: 'e1fb3255ead43dccd3ae0ac2c4f81b32260ca52749936a739669918bbb895411',
13
+ # index: 0,
14
+ # value: 3_000
15
+ # }
16
+ # color_id = Tapyrus::Color::ColorIdentifier.nft(...)
17
+ # utxo2 = {
18
+ # script_pubkey: Tapyrus::Script.parse_from_addr('mu9QMUcB9UCHbQjZJLAuyysQhM9tmFQbPx'),
19
+ # color_id: color_id,
20
+ # txid: 'e1fb3255ead43dccd3ae0ac2c4f81b32260ca52749936a739669918bbb895411',
21
+ # index: 1,
22
+ # value: 3_000
23
+ # }
24
+ #
25
+ # tx = txb
26
+ # .add_utxo(utxo1)
27
+ # .add_utxo(utxo2)
28
+ # .data("0102030405060a0b0c")
29
+ # .reissuable(utxo1[:script_pubkey],'n4jKJN5UMLsAejL1M5CTzQ8npeWoLBLCAH', 10_000)
30
+ # .pay('n4jKJN5UMLsAejL1M5CTzQ8npeWoLBLCAH', 1_000)
31
+ # .build
32
+ #
33
+ class TxBuilder
34
+ attr_reader :outputs, :utxos
35
+
36
+ def initialize
37
+ @utxos = []
38
+ @incomings = {}
39
+ @outgoings = {}
40
+ @outputs = []
41
+ end
42
+
43
+ # Add utxo for transaction input
44
+ # @param utxo [Hash] a hash whose fields are `txid`, `index`, `script_pubkey`, `value`, and `color_id` (color_id is optional)
45
+ def add_utxo(utxo)
46
+ @utxos << utxo
47
+ color_id = utxo[:color_id] || Tapyrus::Color::ColorIdentifier.default
48
+ @incomings[color_id] ||= 0
49
+ @incomings[color_id] += utxo[:value]
50
+ self
51
+ end
52
+
53
+ # Issue reissuable token
54
+ # @param script_pubkey [Tapyrus::Script] the script pubkey in the issue input.
55
+ # @param address [String] p2pkh or p2sh address.
56
+ # @param value [Integer] issued amount.
57
+ def reissuable(script_pubkey, address, value)
58
+ color_id = Tapyrus::Color::ColorIdentifier.reissuable(script_pubkey)
59
+ pay(address, value, color_id)
60
+ end
61
+
62
+ # Issue non reissuable token
63
+ # @param out_point [Tapyrus::OutPoint] the out point at issue input.
64
+ # @param address [String] p2pkh or p2sh address.
65
+ # @param value [Integer] issued amount.
66
+ def non_reissuable(out_point, address, value)
67
+ color_id = Tapyrus::Color::ColorIdentifier.non_reissuable(out_point)
68
+ pay(address, value, color_id)
69
+ end
70
+
71
+ # Issue NFT
72
+ # @param out_point [Tapyrus::OutPoint] the out point at issue input.
73
+ # @param address [String] p2pkh or p2sh address.
74
+ # @param value [Integer] issued amount.
75
+ def nft(out_point, address)
76
+ color_id = Tapyrus::Color::ColorIdentifier.nft(out_point)
77
+ pay(address, 1, color_id)
78
+ end
79
+
80
+ # Create payment output.
81
+ # @param address [String] tapyrus address with Base58 format
82
+ # @param value [Integer] issued or transferred amount
83
+ # @param color_id [Tapyrus::Color::ColorIdentifier] color id
84
+ def pay(address, value, color_id = Tapyrus::Color::ColorIdentifier.default)
85
+ script_pubkey = Tapyrus::Script.parse_from_addr(address)
86
+
87
+ unless color_id.default?
88
+ raise ArgumentError, 'invalid address' if !script_pubkey.p2pkh? && !script_pubkey.p2sh?
89
+ script_pubkey = script_pubkey.add_color(color_id)
90
+ end
91
+
92
+ @outgoings[color_id] ||= 0
93
+ @outgoings[color_id] += value
94
+ @outputs << Tapyrus::TxOut.new(script_pubkey: script_pubkey, value: value)
95
+ self
96
+ end
97
+
98
+ # Create data output
99
+ # @param contents [[String]] array of hex string
100
+ def data(*contents)
101
+ payload = contents.join
102
+ script = Tapyrus::Script.new << Tapyrus::Script::OP_RETURN << payload
103
+ @outputs << Tapyrus::TxOut.new(script_pubkey: script)
104
+ self
105
+ end
106
+
107
+ # Set transaction fee.
108
+ # @param fee [Integer] transaction fee
109
+ def fee(fee)
110
+ @fee = fee
111
+ self
112
+ end
113
+
114
+ # Set address for change.
115
+ # If set, #build method add output for change which has the specified address
116
+ # If not set, transaction built by #build method has no output for change.
117
+ # @param address [String] p2pkh or p2sh address.
118
+ def change_address(address)
119
+ script_pubkey = Tapyrus::Script.parse_from_addr(address)
120
+ raise ArgumentError, 'invalid address' if !script_pubkey.p2pkh? && !script_pubkey.p2sh?
121
+ @change_script_pubkey = script_pubkey
122
+ self
123
+ end
124
+
125
+ # Build transaction
126
+ def build
127
+ tx = Tapyrus::Tx.new
128
+ expand_input(tx)
129
+ @outputs.each { |output| tx.outputs << output }
130
+ add_change(tx) if @change_script_pubkey
131
+ tx
132
+ end
133
+
134
+ private
135
+
136
+ def add_change(tx)
137
+ @incomings.each do |color_id, in_amount|
138
+ out_amount = @outgoings[color_id] || 0
139
+ change, script_pubkey =
140
+ if color_id.default?
141
+ [in_amount - out_amount - estimated_fee, @change_script_pubkey]
142
+ else
143
+ [in_amount - out_amount, @change_script_pubkey.add_color(color_id)]
144
+ end
145
+ tx.outputs << Tapyrus::TxOut.new(script_pubkey: script_pubkey, value: change) if change > 0
146
+ end
147
+ end
148
+
149
+ def expand_input(tx)
150
+ @utxos.each do |utxo|
151
+ tx.inputs << Tapyrus::TxIn.new(out_point: Tapyrus::OutPoint.from_txid(utxo[:txid], utxo[:index]))
152
+ end
153
+ end
154
+
155
+ # Return transaction fee
156
+ def estimated_fee
157
+ @fee
158
+ end
159
+ end
160
+ end
data/lib/tapyrus/tx_in.rb CHANGED
@@ -2,10 +2,8 @@
2
2
  # https://github.com/lian/bitcoin-ruby/blob/master/COPYING
3
3
 
4
4
  module Tapyrus
5
-
6
5
  # transaction input
7
6
  class TxIn
8
-
9
7
  attr_accessor :out_point
10
8
  attr_accessor :script_sig
11
9
  attr_accessor :sequence
@@ -58,11 +56,10 @@ module Tapyrus
58
56
  p
59
57
  end
60
58
 
61
-
62
59
  def to_h
63
60
  sig = script_sig.to_h
64
61
  sig.delete(:type)
65
- h = {txid: out_point.txid, vout: out_point.index, script_sig: sig }
62
+ h = { txid: out_point.txid, vout: out_point.index, script_sig: sig }
66
63
  h[:sequence] = sequence
67
64
  h
68
65
  end
@@ -76,7 +73,5 @@ module Tapyrus
76
73
  return nil unless out_point
77
74
  out_point.tx_hash
78
75
  end
79
-
80
76
  end
81
-
82
77
  end
@@ -2,10 +2,8 @@
2
2
  # https://github.com/lian/bitcoin-ruby/blob/master/COPYING
3
3
 
4
4
  module Tapyrus
5
-
6
5
  # transaction output
7
6
  class TxOut
8
-
9
7
  include OpenAssets::MarkerOutput
10
8
  include Tapyrus::Color::ColoredOutput
11
9
 
@@ -39,7 +37,7 @@ module Tapyrus
39
37
  end
40
38
 
41
39
  def to_h
42
- {value: value_to_btc, script_pubkey: script_pubkey.to_h}
40
+ { value: value_to_btc, script_pubkey: script_pubkey.to_h }
43
41
  end
44
42
 
45
43
  def ==(other)
@@ -65,11 +63,8 @@ module Tapyrus
65
63
  n_size = size
66
64
  n_size += (32 + 4 + 1 + 107 + 4)
67
65
  fee = n_size * Tapyrus.chain_params.dust_relay_fee / 1000
68
- if fee == 0 && n_size != 0
69
- fee = Tapyrus.chain_params.dust_relay_fee > 0 ? 1 : -1
70
- end
66
+ fee = Tapyrus.chain_params.dust_relay_fee > 0 ? 1 : -1 if fee == 0 && n_size != 0
71
67
  fee
72
68
  end
73
69
  end
74
-
75
70
  end
data/lib/tapyrus/util.rb CHANGED
@@ -2,12 +2,10 @@
2
2
  # https://github.com/lian/bitcoin-ruby/blob/master/COPYING
3
3
 
4
4
  module Tapyrus
5
-
6
5
  # tapyrus utility.
7
6
  # following methods can be used as follows.
8
7
  # Tapyrus.pack_var_int(5)
9
8
  module Util
10
-
11
9
  def pack_var_string(payload)
12
10
  pack_var_int(payload.bytesize) + payload
13
11
  end
@@ -18,7 +16,7 @@ module Tapyrus
18
16
  end
19
17
 
20
18
  def pack_var_int(i)
21
- if i < 0xfd
19
+ if i < 0xfd
22
20
  [i].pack('C')
23
21
  elsif i <= 0xffff
24
22
  [0xfd, i].pack('Cv')
@@ -111,10 +109,14 @@ module Tapyrus
111
109
  def decode_base58_address(addr)
112
110
  hex = Base58.decode(addr)
113
111
  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])
112
+ unless [Tapyrus.chain_params.address_version, Tapyrus.chain_params.p2sh_version].include?(hex[0..1])
113
+ raise 'Invalid version bytes.'
114
+ end
115
115
  [hex[2...-8], hex[0..1]]
116
116
  elsif hex.size == 116 && calc_checksum(hex[0...-8]) == hex[-8..-1]
117
- raise 'Invalid version bytes.' unless [Tapyrus.chain_params.cp2pkh_version, Tapyrus.chain_params.cp2sh_version].include?(hex[0..1])
117
+ unless [Tapyrus.chain_params.cp2pkh_version, Tapyrus.chain_params.cp2sh_version].include?(hex[0..1])
118
+ raise 'Invalid version bytes.'
119
+ end
118
120
  [hex[2...-8], hex[0..1]]
119
121
  else
120
122
  raise 'Invalid address.'
@@ -142,15 +144,11 @@ module Tapyrus
142
144
  false
143
145
  end
144
146
  end
145
-
146
147
  end
147
148
 
148
149
  module HexConverter
149
-
150
150
  def to_hex
151
151
  to_payload.bth
152
152
  end
153
-
154
153
  end
155
-
156
154
  end