bitcoinrb 0.5.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +37 -0
  3. data/.rspec_parallel +2 -0
  4. data/.ruby-version +1 -1
  5. data/README.md +11 -1
  6. data/bitcoinrb.gemspec +7 -6
  7. data/lib/bitcoin/block_filter.rb +14 -0
  8. data/lib/bitcoin/chain_params.rb +9 -0
  9. data/lib/bitcoin/chainparams/signet.yml +39 -0
  10. data/lib/bitcoin/constants.rb +45 -4
  11. data/lib/bitcoin/descriptor.rb +1 -1
  12. data/lib/bitcoin/errors.rb +19 -0
  13. data/lib/bitcoin/ext/array_ext.rb +22 -0
  14. data/lib/bitcoin/ext/ecdsa.rb +36 -0
  15. data/lib/bitcoin/ext.rb +1 -0
  16. data/lib/bitcoin/ext_key.rb +36 -20
  17. data/lib/bitcoin/key.rb +85 -28
  18. data/lib/bitcoin/message/addr_v2.rb +34 -0
  19. data/lib/bitcoin/message/base.rb +16 -0
  20. data/lib/bitcoin/message/cfcheckpt.rb +2 -2
  21. data/lib/bitcoin/message/cfheaders.rb +1 -1
  22. data/lib/bitcoin/message/cfilter.rb +1 -1
  23. data/lib/bitcoin/message/fee_filter.rb +1 -1
  24. data/lib/bitcoin/message/filter_load.rb +3 -3
  25. data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
  26. data/lib/bitcoin/message/inventory.rb +1 -1
  27. data/lib/bitcoin/message/merkle_block.rb +1 -1
  28. data/lib/bitcoin/message/network_addr.rb +141 -18
  29. data/lib/bitcoin/message/ping.rb +1 -1
  30. data/lib/bitcoin/message/pong.rb +1 -1
  31. data/lib/bitcoin/message/send_addr_v2.rb +13 -0
  32. data/lib/bitcoin/message/send_cmpct.rb +2 -2
  33. data/lib/bitcoin/message/tx.rb +1 -1
  34. data/lib/bitcoin/message.rb +72 -0
  35. data/lib/bitcoin/message_sign.rb +47 -0
  36. data/lib/bitcoin/mnemonic.rb +2 -2
  37. data/lib/bitcoin/network/peer_discovery.rb +1 -3
  38. data/lib/bitcoin/node/configuration.rb +3 -1
  39. data/lib/bitcoin/node/spv.rb +8 -0
  40. data/lib/bitcoin/opcodes.rb +14 -1
  41. data/lib/bitcoin/payment_code.rb +2 -2
  42. data/lib/bitcoin/payments/payment.pb.rb +1 -1
  43. data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
  44. data/lib/bitcoin/psbt/input.rb +4 -4
  45. data/lib/bitcoin/psbt/output.rb +1 -1
  46. data/lib/bitcoin/psbt/tx.rb +14 -5
  47. data/lib/bitcoin/psbt.rb +8 -0
  48. data/lib/bitcoin/rpc/bitcoin_core_client.rb +1 -1
  49. data/lib/bitcoin/rpc/request_handler.rb +3 -3
  50. data/lib/bitcoin/script/script.rb +80 -30
  51. data/lib/bitcoin/script/script_error.rb +27 -1
  52. data/lib/bitcoin/script/script_interpreter.rb +164 -62
  53. data/lib/bitcoin/script/tx_checker.rb +62 -14
  54. data/lib/bitcoin/secp256k1/native.rb +184 -17
  55. data/lib/bitcoin/secp256k1/ruby.rb +108 -21
  56. data/lib/bitcoin/sighash_generator.rb +157 -0
  57. data/lib/bitcoin/taproot/leaf_node.rb +23 -0
  58. data/lib/bitcoin/taproot/simple_builder.rb +155 -0
  59. data/lib/bitcoin/taproot.rb +45 -0
  60. data/lib/bitcoin/tx.rb +30 -96
  61. data/lib/bitcoin/tx_in.rb +1 -1
  62. data/lib/bitcoin/tx_out.rb +2 -3
  63. data/lib/bitcoin/util.rb +15 -6
  64. data/lib/bitcoin/version.rb +1 -1
  65. data/lib/bitcoin/wallet/account.rb +1 -1
  66. data/lib/bitcoin.rb +32 -24
  67. metadata +58 -18
  68. data/.travis.yml +0 -12
@@ -0,0 +1,155 @@
1
+ module Bitcoin
2
+ module Taproot
3
+
4
+ # Utility class to construct Taproot outputs from internal key and script tree.keyPathSpending
5
+ # SimpleBuilder builds a script tree that places all lock scripts, in the order they are added, as leaf nodes.
6
+ # It is not possible to specify the depth of the locking script or to insert any intermediate nodes.
7
+ class SimpleBuilder
8
+ include Bitcoin::Opcodes
9
+
10
+ attr_reader :internal_key # String with hex format
11
+ attr_reader :branches # List of branch that has two child leaves
12
+
13
+ # Initialize builder.
14
+ # @param [String] internal_key Internal public key with hex format.
15
+ # @param [Array[Bitcoin::Taproot::LeafNode]] leaves (Optional) Array of leaf nodes for each lock condition.
16
+ # @raise [Bitcoin::Taproot::Builder] +internal_pubkey+ dose not xonly public key or leaf in +leaves+ does not instance of Bitcoin::Taproot::LeafNode.
17
+ # @return [Bitcoin::Taproot::SimpleBuilder]
18
+ def initialize(internal_key, leaves = [])
19
+ raise Error, 'Internal public key must be 32 bytes' unless internal_key.htb.bytesize == 32
20
+ raise Error, 'leaf must be Bitcoin::Taproot::LeafNode object' if leaves.find{ |leaf| !leaf.is_a?(Bitcoin::Taproot::LeafNode)}
21
+
22
+ @leaves = leaves
23
+ @branches = leaves.each_slice(2).map.to_a
24
+ @internal_key = internal_key
25
+ end
26
+
27
+ # Add a leaf node to the end of the current branch.
28
+ # @param [Bitcoin::Taproot::LeafNode] leaf Leaf node to be added.
29
+ def add_leaf(leaf)
30
+ raise Error, 'leaf must be Bitcoin::Taproot::LeafNode object' unless leaf.is_a?(Bitcoin::Taproot::LeafNode)
31
+
32
+ if branches.last&.size == 1
33
+ branches.last << leaf
34
+ else
35
+ branches << [leaf]
36
+ end
37
+ self
38
+ end
39
+
40
+ # Add a pair of leaf nodes as a branch. If there is only one, add a branch with only one child.
41
+ # @param [Bitcoin::Taproot::LeafNode] leaf1 Leaf node to be added.
42
+ # @param [Bitcoin::Taproot::LeafNode] leaf2 Leaf node to be added.
43
+ def add_branch(leaf1, leaf2 = nil)
44
+ raise Error, 'leaf1 must be Bitcoin::Taproot::LeafNode object' unless leaf1.is_a?(Bitcoin::Taproot::LeafNode)
45
+ raise Error, 'leaf2 must be Bitcoin::Taproot::LeafNode object' if leaf2 && !leaf2.is_a?(Bitcoin::Taproot::LeafNode)
46
+
47
+ branches << (leaf2.nil? ? [leaf1] : [leaf1, leaf2])
48
+ self
49
+ end
50
+
51
+ # Build P2TR script.
52
+ # @return [Bitcoin::Script] P2TR script.
53
+ def build
54
+ q = tweak_public_key
55
+ Bitcoin::Script.new << OP_1 << q.xonly_pubkey
56
+ end
57
+
58
+ # Compute the tweaked public key.
59
+ # @return [Bitcoin::Key] the tweaked public key
60
+ def tweak_public_key
61
+ Taproot.tweak_public_key(Bitcoin::Key.from_xonly_pubkey(internal_key), merkle_root)
62
+ end
63
+
64
+ # Compute the secret key for a tweaked public key.
65
+ # @param [Bitcoin::Key] key key object contains private key.
66
+ # @return [Bitcoin::Key] secret key for a tweaked public key
67
+ def tweak_private_key(key)
68
+ raise Error, 'Requires private key' unless key.priv_key
69
+
70
+ Taproot.tweak_private_key(key, merkle_root)
71
+ end
72
+
73
+ # Generate control block needed to unlock with script-path.
74
+ # @param [Bitcoin::Taproot::LeafNode] leaf Leaf to use for unlocking.
75
+ # @return [String] control block with binary format.
76
+ def control_block(leaf)
77
+ path = inclusion_proof(leaf)
78
+ parity = tweak_public_key.to_point.has_even_y? ? 0 : 1
79
+ [parity + leaf.leaf_ver].pack("C") + internal_key.htb + path.join
80
+ end
81
+
82
+ # Generate inclusion proof for +leaf+.
83
+ # @param [Bitcoin::Taproot::LeafNode] leaf The leaf node in script tree.
84
+ # @return [Array[String]] Inclusion proof.
85
+ # @raise [Bitcoin::Taproot::Error] If the specified +leaf+ does not exist
86
+ def inclusion_proof(leaf)
87
+ proofs = []
88
+ target_branch = branches.find{|b| b.include?(leaf)}
89
+ raise Error 'Specified leaf does not exist' unless target_branch
90
+
91
+ # flatten each branch
92
+ proofs << hash_value(target_branch.find{|b| b != leaf}) if target_branch.size == 2
93
+ parent_hash = combine_hash(target_branch)
94
+ parents = branches.map {|pair| combine_hash(pair)}
95
+
96
+ until parents.size == 1
97
+ parents = parents.each_slice(2).map do |pair|
98
+ combined = combine_hash(pair)
99
+ unless pair.size == 1
100
+ if hash_value(pair[0]) == parent_hash
101
+ proofs << hash_value(pair[1])
102
+ parent_hash = combined
103
+ elsif hash_value(pair[1]) == parent_hash
104
+ proofs << hash_value(pair[0])
105
+ parent_hash = combined
106
+ end
107
+ end
108
+ combined
109
+ end
110
+ end
111
+ proofs
112
+ end
113
+
114
+ private
115
+
116
+ # Compute tweak from script tree.
117
+ # @return [String] tweak with binary format.
118
+ def tweak
119
+ Taproot.tweak(Bitcoin::Key.from_xonly_pubkey(internal_key), merkle_root)
120
+ end
121
+
122
+ # Calculate merkle root from branches.
123
+ # @return [String] merkle root with hex format.
124
+ def merkle_root
125
+ parents = branches.map {|pair| combine_hash(pair)}
126
+ if parents.empty?
127
+ parents = ['']
128
+ elsif parents.size == 1
129
+ parents = [combine_hash(parents)]
130
+ else
131
+ parents = parents.each_slice(2).map { |pair| combine_hash(pair) } until parents.size == 1
132
+ end
133
+ parents.first.bth
134
+ end
135
+
136
+ def combine_hash(pair)
137
+ if pair.size == 1
138
+ hash_value(pair[0])
139
+ else
140
+ hash1 = hash_value(pair[0])
141
+ hash2 = hash_value(pair[1])
142
+
143
+ # Lexicographically sort a and b's hash, and compute parent hash.
144
+ payload = hash1.bth < hash2.bth ? hash1 + hash2 : hash2 + hash1
145
+ Bitcoin.tagged_hash('TapBranch', payload)
146
+ end
147
+ end
148
+
149
+ def hash_value(leaf_or_branch)
150
+ leaf_or_branch.is_a?(LeafNode) ? leaf_or_branch.leaf_hash : leaf_or_branch
151
+ end
152
+ end
153
+ end
154
+
155
+ end
@@ -0,0 +1,45 @@
1
+ module Bitcoin
2
+ module Taproot
3
+
4
+ class Error < StandardError; end
5
+
6
+ autoload :LeafNode, 'bitcoin/taproot/leaf_node'
7
+ autoload :SimpleBuilder, 'bitcoin/taproot/simple_builder'
8
+
9
+ module_function
10
+
11
+ # Calculate tweak value from +internal_pubkey+ and +merkle_root+.
12
+ # @param [Bitcoin::Key] internal_key Internal key with hex format(x-only public key).
13
+ # @param [String] merkle_root Merkle root value of script tree with hex format.
14
+ # @return [String] teak value with binary format.
15
+ def tweak(internal_key, merkle_root)
16
+ raise Error, 'internal_key must be Bitcoin::Key object.' unless internal_key.is_a?(Bitcoin::Key)
17
+
18
+ merkle_root ||= ''
19
+ t = Bitcoin.tagged_hash('TapTweak', internal_key.xonly_pubkey.htb + merkle_root.htb)
20
+ raise Error, 'tweak value exceeds the curve order' if t.bti >= ECDSA::Group::Secp256k1.order
21
+
22
+ t
23
+ end
24
+
25
+ # Generate tweak public key form +internal_pubkey+ and +merkle_root+.
26
+ # @param [Bitcoin::Key] internal_key Internal key with hex format(x-only public key).
27
+ # @param [String] merkle_root Merkle root value of script tree with hex format.
28
+ # @return [Bitcoin::Key] Tweaked public key.
29
+ def tweak_public_key(internal_key, merkle_root)
30
+ t = tweak(internal_key, merkle_root)
31
+ key = Bitcoin::Key.new(priv_key: t.bth, key_type: Key::TYPES[:compressed])
32
+ Bitcoin::Key.from_point(key.to_point + internal_key.to_point)
33
+ end
34
+
35
+ # Generate tweak private key
36
+ #
37
+ def tweak_private_key(internal_private_key, merkle_root)
38
+ p = internal_private_key.to_point
39
+ private_key = p.has_even_y? ? internal_private_key.priv_key.to_i(16) :
40
+ ECDSA::Group::Secp256k1.order - internal_private_key.priv_key.to_i(16)
41
+ t = tweak(internal_private_key, merkle_root)
42
+ Bitcoin::Key.new(priv_key: ((t.bti + private_key) % ECDSA::Group::Secp256k1.order).to_even_length_hex)
43
+ end
44
+ end
45
+ end
data/lib/bitcoin/tx.rb CHANGED
@@ -33,21 +33,21 @@ module Bitcoin
33
33
  alias_method :in, :inputs
34
34
  alias_method :out, :outputs
35
35
 
36
- def self.parse_from_payload(payload, non_witness: false)
36
+ def self.parse_from_payload(payload, non_witness: false, strict: false)
37
37
  buf = payload.is_a?(String) ? StringIO.new(payload) : payload
38
38
  tx = new
39
- tx.version = buf.read(4).unpack('V').first
39
+ tx.version = buf.read(4).unpack1('V')
40
40
 
41
41
  in_count = Bitcoin.unpack_var_int_from_io(buf)
42
- witness = false
42
+ has_witness = false
43
43
  if in_count.zero? && !non_witness
44
44
  tx.marker = 0
45
- tx.flag = buf.read(1).unpack('c').first
45
+ tx.flag = buf.read(1).unpack1('c')
46
46
  if tx.flag.zero?
47
47
  buf.pos -= 1
48
48
  else
49
49
  in_count = Bitcoin.unpack_var_int_from_io(buf)
50
- witness = true
50
+ has_witness = true
51
51
  end
52
52
  end
53
53
 
@@ -60,14 +60,14 @@ module Bitcoin
60
60
  tx.outputs << TxOut.parse_from_payload(buf)
61
61
  end
62
62
 
63
- if witness
63
+ if has_witness
64
64
  in_count.times do |i|
65
65
  tx.inputs[i].script_witness = Bitcoin::ScriptWitness.parse_from_payload(buf)
66
66
  end
67
67
  end
68
68
 
69
- tx.lock_time = buf.read(4).unpack('V').first
70
-
69
+ raise ArgumentError, 'Transaction has unexpected data.' if strict && (buf.pos + 4) != buf.length
70
+ tx.lock_time = buf.read(4).unpack1('V')
71
71
  tx
72
72
  end
73
73
 
@@ -188,22 +188,26 @@ module Bitcoin
188
188
  # @param [Integer] input_index input index.
189
189
  # @param [Integer] hash_type signature hash type
190
190
  # @param [Bitcoin::Script] output_script script pubkey or script code. if script pubkey is P2WSH, set witness script to this.
191
+ # @param [Hash] opts Data required for each sig version (amount and skip_separator_index params can also be set to this parameter)
191
192
  # @param [Integer] amount bitcoin amount locked in input. required for witness input only.
192
193
  # @param [Integer] skip_separator_index If output_script is P2WSH and output_script contains any OP_CODESEPARATOR,
193
194
  # 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.
194
- def sighash_for_input(input_index, output_script, hash_type: SIGHASH_TYPE[:all],
195
- sig_version: :base, amount: nil, skip_separator_index: 0)
195
+ # @param [Array[Bitcoin::TxOut] prevouts Previous outputs referenced by all Tx inputs, required for taproot.
196
+ # @return [String] signature hash with binary format.
197
+ def sighash_for_input(input_index, output_script = nil, opts: {}, hash_type: SIGHASH_TYPE[:all],
198
+ sig_version: :base, amount: nil, skip_separator_index: 0, prevouts: [])
196
199
  raise ArgumentError, 'input_index must be specified.' unless input_index
197
200
  raise ArgumentError, 'does not exist input corresponding to input_index.' if input_index >= inputs.size
198
- raise ArgumentError, 'script_pubkey must be specified.' unless output_script
199
- raise ArgumentError, 'unsupported sig version specified.' unless SIG_VERSION.include?(sig_version)
200
-
201
- if sig_version == :witness_v0
202
- raise ArgumentError, 'amount must be specified.' unless amount
203
- sighash_for_witness(input_index, output_script, hash_type, amount, skip_separator_index)
204
- else
205
- sighash_for_legacy(input_index, output_script, hash_type)
206
- end
201
+ raise ArgumentError, 'script_pubkey must be specified.' if [:base, :witness_v0].include?(sig_version) && output_script.nil?
202
+
203
+ opts[:amount] = amount if amount
204
+ opts[:skip_separator_index] = skip_separator_index
205
+ opts[:sig_version] = sig_version
206
+ opts[:script_code] = output_script
207
+ opts[:prevouts] = prevouts
208
+ opts[:last_code_separator_pos] ||= 0xffffffff
209
+ sig_hash_gen = SigHashGenerator.load(sig_version)
210
+ sig_hash_gen.generate(self, input_index, hash_type, opts)
207
211
  end
208
212
 
209
213
  # verify input signature.
@@ -211,7 +215,9 @@ module Bitcoin
211
215
  # @param [Bitcoin::Script] script_pubkey the script pubkey for target input.
212
216
  # @param [Integer] amount the amount of bitcoin, require for witness program only.
213
217
  # @param [Array] flags the flags used when execute script interpreter.
214
- def verify_input_sig(input_index, script_pubkey, amount: nil, flags: STANDARD_SCRIPT_VERIFY_FLAGS)
218
+ # @param [Array[Bitcoin::TxOut]] prevouts Previous outputs referenced by all Tx inputs, required for taproot.
219
+ # @return [Boolean] result
220
+ def verify_input_sig(input_index, script_pubkey, amount: nil, flags: STANDARD_SCRIPT_VERIFY_FLAGS, prevouts: [])
215
221
  script_sig = inputs[input_index].script_sig
216
222
  has_witness = inputs[input_index].has_witness?
217
223
 
@@ -222,7 +228,7 @@ module Bitcoin
222
228
  end
223
229
 
224
230
  if has_witness
225
- verify_input_sig_for_witness(input_index, script_pubkey, amount, flags)
231
+ verify_input_sig_for_witness(input_index, script_pubkey, amount, flags, prevouts)
226
232
  else
227
233
  verify_input_sig_for_legacy(input_index, script_pubkey, flags)
228
234
  end
@@ -245,73 +251,6 @@ module Bitcoin
245
251
 
246
252
  private
247
253
 
248
- # generate sighash with legacy format
249
- def sighash_for_legacy(index, script_code, hash_type)
250
- ins = inputs.map.with_index do |i, idx|
251
- if idx == index
252
- i.to_payload(script_code.delete_opcode(Bitcoin::Opcodes::OP_CODESEPARATOR))
253
- else
254
- case hash_type & 0x1f
255
- when SIGHASH_TYPE[:none], SIGHASH_TYPE[:single]
256
- i.to_payload(Bitcoin::Script.new, 0)
257
- else
258
- i.to_payload(Bitcoin::Script.new)
259
- end
260
- end
261
- end
262
-
263
- outs = outputs.map(&:to_payload)
264
- out_size = Bitcoin.pack_var_int(outputs.size)
265
-
266
- case hash_type & 0x1f
267
- when SIGHASH_TYPE[:none]
268
- outs = ''
269
- out_size = Bitcoin.pack_var_int(0)
270
- when SIGHASH_TYPE[:single]
271
- return "\x01".ljust(32, "\x00") if index >= outputs.size
272
- outs = outputs[0...(index + 1)].map.with_index { |o, idx| (idx == index) ? o.to_payload : o.to_empty_payload }.join
273
- out_size = Bitcoin.pack_var_int(index + 1)
274
- end
275
-
276
- if hash_type & SIGHASH_TYPE[:anyonecanpay] != 0
277
- ins = [ins[index]]
278
- end
279
-
280
- buf = [[version].pack('V'), Bitcoin.pack_var_int(ins.size),
281
- ins, out_size, outs, [lock_time, hash_type].pack('VV')].join
282
-
283
- Bitcoin.double_sha256(buf)
284
- end
285
-
286
- # generate sighash with BIP-143 format
287
- # https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
288
- def sighash_for_witness(index, script_pubkey_or_script_code, hash_type, amount, skip_separator_index)
289
- hash_prevouts = Bitcoin.double_sha256(inputs.map{|i|i.out_point.to_payload}.join)
290
- hash_sequence = Bitcoin.double_sha256(inputs.map{|i|[i.sequence].pack('V')}.join)
291
- outpoint = inputs[index].out_point.to_payload
292
- amount = [amount].pack('Q')
293
- nsequence = [inputs[index].sequence].pack('V')
294
- hash_outputs = Bitcoin.double_sha256(outputs.map{|o|o.to_payload}.join)
295
-
296
- script_code = script_pubkey_or_script_code.to_script_code(skip_separator_index)
297
-
298
- case (hash_type & 0x1f)
299
- when SIGHASH_TYPE[:single]
300
- hash_outputs = index >= outputs.size ? "\x00".ljust(32, "\x00") : Bitcoin.double_sha256(outputs[index].to_payload)
301
- hash_sequence = "\x00".ljust(32, "\x00")
302
- when SIGHASH_TYPE[:none]
303
- hash_sequence = hash_outputs = "\x00".ljust(32, "\x00")
304
- end
305
-
306
- if (hash_type & SIGHASH_TYPE[:anyonecanpay]) != 0
307
- hash_prevouts = hash_sequence ="\x00".ljust(32, "\x00")
308
- end
309
-
310
- buf = [ [version].pack('V'), hash_prevouts, hash_sequence, outpoint,
311
- script_code ,amount, nsequence, hash_outputs, [@lock_time, hash_type].pack('VV')].join
312
- Bitcoin.double_sha256(buf)
313
- end
314
-
315
254
  # verify input signature for legacy tx.
316
255
  def verify_input_sig_for_legacy(input_index, script_pubkey, flags)
317
256
  script_sig = inputs[input_index].script_sig
@@ -322,16 +261,11 @@ module Bitcoin
322
261
  end
323
262
 
324
263
  # verify input signature for witness tx.
325
- def verify_input_sig_for_witness(input_index, script_pubkey, amount, flags)
326
- flags |= SCRIPT_VERIFY_WITNESS
327
- flags |= SCRIPT_VERIFY_WITNESS_PUBKEYTYPE
328
- checker = Bitcoin::TxChecker.new(tx: self, input_index: input_index, amount: amount)
264
+ def verify_input_sig_for_witness(input_index, script_pubkey, amount, flags, prevouts)
265
+ checker = Bitcoin::TxChecker.new(tx: self, input_index: input_index, amount: amount, prevouts: prevouts)
329
266
  interpreter = Bitcoin::ScriptInterpreter.new(checker: checker, flags: flags)
330
267
  i = inputs[input_index]
331
-
332
- script_sig = i.script_sig
333
- witness = i.script_witness
334
- interpreter.verify_script(script_sig, script_pubkey, witness)
268
+ interpreter.verify_script(i.script_sig, script_pubkey, i.script_witness)
335
269
  end
336
270
 
337
271
  end
data/lib/bitcoin/tx_in.rb CHANGED
@@ -42,7 +42,7 @@ module Bitcoin
42
42
  else
43
43
  i.script_sig = Script.parse_from_payload(buf.read(sig_length))
44
44
  end
45
- i.sequence = buf.read(4).unpack('V').first
45
+ i.sequence = buf.read(4).unpack1('V')
46
46
  i
47
47
  end
48
48
 
@@ -18,14 +18,13 @@ module Bitcoin
18
18
 
19
19
  def self.parse_from_payload(payload)
20
20
  buf = payload.is_a?(String) ? StringIO.new(payload) : payload
21
- value = buf.read(8).unpack('q').first
21
+ value = buf.read(8).unpack1('q')
22
22
  script_size = Bitcoin.unpack_var_int_from_io(buf)
23
23
  new(value: value, script_pubkey: Script.parse_from_payload(buf.read(script_size)))
24
24
  end
25
25
 
26
26
  def to_payload
27
- s = script_pubkey.to_payload
28
- [value].pack('Q') << Bitcoin.pack_var_int(s.length) << s
27
+ [value].pack('Q') << script_pubkey.to_payload(true)
29
28
  end
30
29
 
31
30
  def to_empty_payload
data/lib/bitcoin/util.rb CHANGED
@@ -33,7 +33,7 @@ module Bitcoin
33
33
 
34
34
  # @return an integer for a valid payload, otherwise nil
35
35
  def unpack_var_int(payload)
36
- case payload.unpack('C').first
36
+ case payload.unpack1('C')
37
37
  when 0xfd
38
38
  payload.unpack('xva*')
39
39
  when 0xfe
@@ -47,14 +47,14 @@ module Bitcoin
47
47
 
48
48
  # @return an integer for a valid payload, otherwise nil
49
49
  def unpack_var_int_from_io(buf)
50
- uchar = buf.read(1)&.unpack('C')&.first
50
+ uchar = buf.read(1)&.unpack1('C')
51
51
  case uchar
52
52
  when 0xfd
53
- buf.read(2)&.unpack('v')&.first
53
+ buf.read(2)&.unpack1('v')
54
54
  when 0xfe
55
- buf.read(4)&.unpack('V')&.first
55
+ buf.read(4)&.unpack1('V')
56
56
  when 0xff
57
- buf.read(8)&.unpack('Q')&.first
57
+ buf.read(8)&.unpack1('Q')
58
58
  else
59
59
  uchar
60
60
  end
@@ -79,7 +79,7 @@ module Bitcoin
79
79
 
80
80
  # byte convert to the sequence of bits packed eight in a byte with the least significant bit first.
81
81
  def byte_to_bit(byte)
82
- byte.unpack('b*').first
82
+ byte.unpack1('b*')
83
83
  end
84
84
 
85
85
  # padding zero to the left of binary string until bytesize.
@@ -96,6 +96,15 @@ module Bitcoin
96
96
  Digest::RMD160.hexdigest(Digest::SHA256.digest(hex.htb))
97
97
  end
98
98
 
99
+ # Generate tagged hash value.
100
+ # @param [String] tag tag value.
101
+ # @param [String] msg the message to be hashed.
102
+ # @return [String] the hash value with binary format.
103
+ def tagged_hash(tag, msg)
104
+ tag_hash = Digest::SHA256.digest(tag)
105
+ Digest::SHA256.digest(tag_hash + tag_hash + msg)
106
+ end
107
+
99
108
  # encode Base58 check address.
100
109
  # @param [String] hex the address payload.
101
110
  # @param [String] addr_version the address version for P2PKH and P2SH.
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "0.5.0"
2
+ VERSION = "0.9.0"
3
3
  end
@@ -43,7 +43,7 @@ module Bitcoin
43
43
 
44
44
  def to_payload
45
45
  payload = account_key.to_payload
46
- payload << Bitcoin.pack_var_string(name.unpack('H*').first.htb)
46
+ payload << Bitcoin.pack_var_string(name.unpack1('H*').htb)
47
47
  payload << [purpose, index, receive_depth, change_depth, lookahead].pack('I*')
48
48
  payload
49
49
  end
data/lib/bitcoin.rb CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  require 'bitcoin/version'
5
5
  require 'eventmachine'
6
- require 'ecdsa'
6
+ require 'schnorr'
7
7
  require 'securerandom'
8
8
  require 'json'
9
9
  require 'bech32'
@@ -58,8 +58,13 @@ module Bitcoin
58
58
  autoload :Aezeed, 'bitcoin/aezeed'
59
59
  autoload :PaymentCode, 'bitcoin/payment_code'
60
60
  autoload :BIP85Entropy, 'bitcoin/bip85_entropy'
61
+ autoload :Errors, 'bitcoin/errors'
62
+ autoload :SigHashGenerator, 'bitcoin/sighash_generator'
63
+ autoload :MessageSign, 'bitcoin/message_sign'
64
+ autoload :Taproot, 'bitcoin/taproot'
61
65
 
62
66
  require_relative 'bitcoin/constants'
67
+ require_relative 'bitcoin/ext/ecdsa'
63
68
 
64
69
  extend Util
65
70
 
@@ -67,7 +72,7 @@ module Bitcoin
67
72
 
68
73
  # set bitcoin network chain params
69
74
  def self.chain_params=(name)
70
- raise "chain params for #{name} is not defined." unless %i(mainnet testnet regtest).include?(name.to_sym)
75
+ raise "chain params for #{name} is not defined." unless %i(mainnet testnet regtest signet).include?(name.to_sym)
71
76
  @current_chain = nil
72
77
  @chain_param = name.to_sym
73
78
  end
@@ -82,6 +87,8 @@ module Bitcoin
82
87
  @current_chain = Bitcoin::ChainParams.testnet
83
88
  when :regtest
84
89
  @current_chain = Bitcoin::ChainParams.regtest
90
+ when :signet
91
+ @current_chain = Bitcoin::ChainParams.signet
85
92
  end
86
93
  @current_chain
87
94
  end
@@ -108,7 +115,7 @@ module Bitcoin
108
115
  class ::String
109
116
  # binary convert to hex string
110
117
  def bth
111
- unpack('H*').first
118
+ unpack1('H*')
112
119
  end
113
120
 
114
121
  # hex string convert to binary
@@ -128,14 +135,7 @@ module Bitcoin
128
135
 
129
136
  # get opcode
130
137
  def opcode
131
- case encoding
132
- when Encoding::ASCII_8BIT
133
- each_byte.next
134
- when Encoding::US_ASCII
135
- ord
136
- else
137
- to_i
138
- end
138
+ force_encoding(Encoding::ASCII_8BIT).ord
139
139
  end
140
140
 
141
141
  def opcode?
@@ -165,6 +165,27 @@ module Bitcoin
165
165
  self[offset..-1]
166
166
  end
167
167
 
168
+ def valid_pushdata_length?
169
+ buf = StringIO.new(self)
170
+ opcode = buf.read(1).ord
171
+ offset = 1
172
+ return false if buf.eof?
173
+ len = case opcode
174
+ when Bitcoin::Opcodes::OP_PUSHDATA1
175
+ offset += 1
176
+ buf.read(1).unpack1('C')
177
+ when Bitcoin::Opcodes::OP_PUSHDATA2
178
+ offset += 2
179
+ buf.read(2).unpack1('v')
180
+ when Bitcoin::Opcodes::OP_PUSHDATA4
181
+ offset += 4
182
+ buf.read(4).unpack1('V')
183
+ else
184
+ opcode
185
+ end
186
+ self.bytesize == len + offset
187
+ end
188
+
168
189
  # whether value is hex or not hex
169
190
  # @return [Boolean] return true if data is hex
170
191
  def valid_hex?
@@ -219,17 +240,4 @@ module Bitcoin
219
240
  end
220
241
  end
221
242
 
222
- class ::ECDSA::Signature
223
- # convert signature to der string.
224
- def to_der
225
- ECDSA::Format::SignatureDerString.encode(self)
226
- end
227
- end
228
-
229
- class ::ECDSA::Point
230
- def to_hex(compression = true)
231
- ECDSA::Format::PointOctetString.encode(self, compression: compression).bth
232
- end
233
- end
234
-
235
243
  end