tapyrus 0.2.7 → 0.2.12

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