bitcoinrb 0.6.0 → 1.0.0

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