bitcoinrb 0.5.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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