bitcoinrb 0.6.0 → 1.0.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.
@@ -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,13 +33,13 @@ 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
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
45
  tx.flag = buf.read(1).unpack1('c')
@@ -47,7 +47,7 @@ module Bitcoin
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
+ raise ArgumentError, 'Transaction has unexpected data.' if strict && (buf.pos + 4) != buf.length
69
70
  tx.lock_time = buf.read(4).unpack1('V')
70
-
71
71
  tx
72
72
  end
73
73
 
@@ -192,8 +192,10 @@ module Bitcoin
192
192
  # @param [Integer] amount bitcoin amount locked in input. required for witness input only.
193
193
  # @param [Integer] skip_separator_index If output_script is P2WSH and output_script contains any OP_CODESEPARATOR,
194
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.
195
+ # @param [Array[Bitcoin::TxOut] prevouts Previous outputs referenced by all Tx inputs, required for taproot.
196
+ # @return [String] signature hash with binary format.
195
197
  def sighash_for_input(input_index, output_script = nil, opts: {}, hash_type: SIGHASH_TYPE[:all],
196
- sig_version: :base, amount: nil, skip_separator_index: 0)
198
+ sig_version: :base, amount: nil, skip_separator_index: 0, prevouts: [])
197
199
  raise ArgumentError, 'input_index must be specified.' unless input_index
198
200
  raise ArgumentError, 'does not exist input corresponding to input_index.' if input_index >= inputs.size
199
201
  raise ArgumentError, 'script_pubkey must be specified.' if [:base, :witness_v0].include?(sig_version) && output_script.nil?
@@ -202,6 +204,8 @@ module Bitcoin
202
204
  opts[:skip_separator_index] = skip_separator_index
203
205
  opts[:sig_version] = sig_version
204
206
  opts[:script_code] = output_script
207
+ opts[:prevouts] = prevouts
208
+ opts[:last_code_separator_pos] ||= 0xffffffff
205
209
  sig_hash_gen = SigHashGenerator.load(sig_version)
206
210
  sig_hash_gen.generate(self, input_index, hash_type, opts)
207
211
  end
@@ -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
@@ -255,16 +261,11 @@ module Bitcoin
255
261
  end
256
262
 
257
263
  # verify input signature for witness tx.
258
- def verify_input_sig_for_witness(input_index, script_pubkey, amount, flags)
259
- flags |= SCRIPT_VERIFY_WITNESS
260
- flags |= SCRIPT_VERIFY_WITNESS_PUBKEYTYPE
261
- 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)
262
266
  interpreter = Bitcoin::ScriptInterpreter.new(checker: checker, flags: flags)
263
267
  i = inputs[input_index]
264
-
265
- script_sig = i.script_sig
266
- witness = i.script_witness
267
- interpreter.verify_script(script_sig, script_pubkey, witness)
268
+ interpreter.verify_script(i.script_sig, script_pubkey, i.script_witness)
268
269
  end
269
270
 
270
271
  end
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "0.6.0"
2
+ VERSION = "1.0.0"
3
3
  end
data/lib/bitcoin.rb CHANGED
@@ -60,6 +60,8 @@ module Bitcoin
60
60
  autoload :BIP85Entropy, 'bitcoin/bip85_entropy'
61
61
  autoload :Errors, 'bitcoin/errors'
62
62
  autoload :SigHashGenerator, 'bitcoin/sighash_generator'
63
+ autoload :MessageSign, 'bitcoin/message_sign'
64
+ autoload :Taproot, 'bitcoin/taproot'
63
65
 
64
66
  require_relative 'bitcoin/constants'
65
67
  require_relative 'bitcoin/ext/ecdsa'
@@ -133,14 +135,7 @@ module Bitcoin
133
135
 
134
136
  # get opcode
135
137
  def opcode
136
- case encoding
137
- when Encoding::ASCII_8BIT
138
- each_byte.next
139
- when Encoding::US_ASCII
140
- ord
141
- else
142
- to_i
143
- end
138
+ force_encoding(Encoding::ASCII_8BIT).ord
144
139
  end
145
140
 
146
141
  def opcode?
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bitcoinrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - azuchi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-20 00:00:00.000000000 Z
11
+ date: 2021-11-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ecdsa
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 1.0.3
61
+ version: 1.1.0
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 1.0.3
68
+ version: 1.1.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: daemon-spawn
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -198,14 +198,28 @@ dependencies:
198
198
  requirements:
199
199
  - - ">="
200
200
  - !ruby/object:Gem::Version
201
- version: 0.3.2
201
+ version: 0.4.0
202
202
  type: :runtime
203
203
  prerelease: false
204
204
  version_requirements: !ruby/object:Gem::Requirement
205
205
  requirements:
206
206
  - - ">="
207
207
  - !ruby/object:Gem::Version
208
- version: 0.3.2
208
+ version: 0.4.0
209
+ - !ruby/object:Gem::Dependency
210
+ name: base32
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: 0.3.4
216
+ type: :runtime
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ">="
221
+ - !ruby/object:Gem::Version
222
+ version: 0.3.4
209
223
  - !ruby/object:Gem::Dependency
210
224
  name: leveldb-native
211
225
  requirement: !ruby/object:Gem::Requirement
@@ -290,6 +304,20 @@ dependencies:
290
304
  - - ">="
291
305
  - !ruby/object:Gem::Version
292
306
  version: 3.11.1
307
+ - !ruby/object:Gem::Dependency
308
+ name: parallel
309
+ requirement: !ruby/object:Gem::Requirement
310
+ requirements:
311
+ - - ">="
312
+ - !ruby/object:Gem::Version
313
+ version: 1.20.1
314
+ type: :development
315
+ prerelease: false
316
+ version_requirements: !ruby/object:Gem::Requirement
317
+ requirements:
318
+ - - ">="
319
+ - !ruby/object:Gem::Version
320
+ version: 1.20.1
293
321
  description: The implementation of Bitcoin Protocol for Ruby.
294
322
  email:
295
323
  - azuchi@chaintope.com
@@ -299,11 +327,12 @@ executables:
299
327
  extensions: []
300
328
  extra_rdoc_files: []
301
329
  files:
330
+ - ".github/workflows/ruby.yml"
302
331
  - ".gitignore"
303
332
  - ".rspec"
333
+ - ".rspec_parallel"
304
334
  - ".ruby-gemset"
305
335
  - ".ruby-version"
306
- - ".travis.yml"
307
336
  - CODE_OF_CONDUCT.md
308
337
  - Gemfile
309
338
  - LICENSE.txt
@@ -332,6 +361,7 @@ files:
332
361
  - lib/bitcoin/descriptor.rb
333
362
  - lib/bitcoin/errors.rb
334
363
  - lib/bitcoin/ext.rb
364
+ - lib/bitcoin/ext/array_ext.rb
335
365
  - lib/bitcoin/ext/ecdsa.rb
336
366
  - lib/bitcoin/ext/json_parser.rb
337
367
  - lib/bitcoin/ext_key.rb
@@ -342,6 +372,7 @@ files:
342
372
  - lib/bitcoin/merkle_tree.rb
343
373
  - lib/bitcoin/message.rb
344
374
  - lib/bitcoin/message/addr.rb
375
+ - lib/bitcoin/message/addr_v2.rb
345
376
  - lib/bitcoin/message/base.rb
346
377
  - lib/bitcoin/message/block.rb
347
378
  - lib/bitcoin/message/block_transaction_request.rb
@@ -379,11 +410,13 @@ files:
379
410
  - lib/bitcoin/message/pong.rb
380
411
  - lib/bitcoin/message/prefilled_tx.rb
381
412
  - lib/bitcoin/message/reject.rb
413
+ - lib/bitcoin/message/send_addr_v2.rb
382
414
  - lib/bitcoin/message/send_cmpct.rb
383
415
  - lib/bitcoin/message/send_headers.rb
384
416
  - lib/bitcoin/message/tx.rb
385
417
  - lib/bitcoin/message/ver_ack.rb
386
418
  - lib/bitcoin/message/version.rb
419
+ - lib/bitcoin/message_sign.rb
387
420
  - lib/bitcoin/mnemonic.rb
388
421
  - lib/bitcoin/mnemonic/wordlist/chinese_simplified.txt
389
422
  - lib/bitcoin/mnemonic/wordlist/chinese_traditional.txt
@@ -443,6 +476,9 @@ files:
443
476
  - lib/bitcoin/store/db/level_db.rb
444
477
  - lib/bitcoin/store/spv_chain.rb
445
478
  - lib/bitcoin/store/utxo_db.rb
479
+ - lib/bitcoin/taproot.rb
480
+ - lib/bitcoin/taproot/leaf_node.rb
481
+ - lib/bitcoin/taproot/simple_builder.rb
446
482
  - lib/bitcoin/tx.rb
447
483
  - lib/bitcoin/tx_in.rb
448
484
  - lib/bitcoin/tx_out.rb
@@ -478,7 +514,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
478
514
  - !ruby/object:Gem::Version
479
515
  version: '0'
480
516
  requirements: []
481
- rubygems_version: 3.2.3
517
+ rubygems_version: 3.2.22
482
518
  signing_key:
483
519
  specification_version: 4
484
520
  summary: The implementation of Bitcoin Protocol for Ruby.
data/.travis.yml DELETED
@@ -1,13 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.4.10
4
- - 2.5.8
5
- - 2.6.6
6
- - 2.7.2
7
- - 3.0.0
8
- addons:
9
- apt:
10
- packages:
11
- - libleveldb-dev
12
- script:
13
- - bundle exec parallel_test spec/ -n 6 --type rspec