bitcoinrb 0.7.0 → 0.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5026fc4446c5a88954149551d7168d8022e5aa41cd45cbf86b44ce62fdb308a5
4
- data.tar.gz: a6a3708940e5eca7d8eba4ac71ca941e98ba5af7e09f516ea6e7583b38299c80
3
+ metadata.gz: af836e45167490d93d1d9b5d8f29f4e473a78dc314b98cd789bb47e347a42037
4
+ data.tar.gz: 020fe745057f3b987c8bd8828be9f7f657d3085a7d8ee37cf0218b0761202325
5
5
  SHA512:
6
- metadata.gz: 0f3ce5cc15d9de9c0fbb578ec179a81148228bb7f12c9937f6a3fe6f73585d09f6d62798abe1255e95e5d415c0a0e7fdeeb51457245b16565c8908b76d2d0249
7
- data.tar.gz: fc3f64635b4330c15152b9a38b49a2ec7b324d3a7940cd7a51c64a601d6f9e89a6731014a9f7d2c86cef8a0453b18beb39c5ecec288b8a77c24d73d615c2aebf
6
+ metadata.gz: 0db2c744f372a2e71c337cb8970bb8cad81f37aa81824504d5fe6ee46819516ca6869c3cab0af1474aa0906905496af3e8f97eacc6cdaf9b7eced0a6cd247ff2
7
+ data.tar.gz: dab1256db8085c43ec5ab5d784d465cbdb3d74e663d4f052fd76161decacd9fb5fe22b3c2f99cc20381dda8ea203d63866db45e6e8c8ed6d8b4a2d4c27acfe89
@@ -0,0 +1,37 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ push:
12
+ branches: [ master ]
13
+ pull_request:
14
+ branches: [ master ]
15
+
16
+ jobs:
17
+ test:
18
+
19
+ runs-on: ubuntu-latest
20
+ strategy:
21
+ matrix:
22
+ ruby-version: ['2.6', '2.7', '3.0']
23
+
24
+ steps:
25
+ - uses: actions/checkout@v2
26
+ - name: Install leveldb
27
+ run: sudo apt-get install libleveldb-dev
28
+ - name: Set up Ruby
29
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
30
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
31
+ # uses: ruby/setup-ruby@v1
32
+ uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
33
+ with:
34
+ ruby-version: ${{ matrix.ruby-version }}
35
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
36
+ - name: Run tests
37
+ run: bundle exec rake spec
data/.rspec_parallel ADDED
@@ -0,0 +1,2 @@
1
+ --format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log
2
+ --format ParallelTests::RSpec::SummaryLogger --out tmp/spec_summary.log
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Bitcoinrb [![Build Status](https://travis-ci.org/chaintope/bitcoinrb.svg?branch=master)](https://travis-ci.org/chaintope/bitcoinrb) [![Gem Version](https://badge.fury.io/rb/bitcoinrb.svg)](https://badge.fury.io/rb/bitcoinrb) [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) <img src="http://segwit.co/static/public/images/logo.png" width="100">
1
+ # Bitcoinrb [![Build Status](https://github.com/chaintope/bitcoinrb/actions/workflows/ruby.yml/badge.svg?branch=master)](https://github.com/chaintope/bitcoinrb/actions/workflows/ruby.yml) [![Gem Version](https://badge.fury.io/rb/bitcoinrb.svg)](https://badge.fury.io/rb/bitcoinrb) [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) <img src="http://segwit.co/static/public/images/logo.png" width="100">
2
2
 
3
3
 
4
4
  Bitcoinrb is a Ruby implementation of Bitcoin Protocol.
data/bitcoinrb.gemspec CHANGED
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
33
33
  spec.add_runtime_dependency 'siphash'
34
34
  spec.add_runtime_dependency 'protobuf', '3.8.5'
35
35
  spec.add_runtime_dependency 'json_pure', '>= 2.3.1'
36
- spec.add_runtime_dependency 'bip-schnorr', '>= 0.3.2'
36
+ spec.add_runtime_dependency 'bip-schnorr', '>= 0.4.0'
37
37
  spec.add_runtime_dependency 'base32', '>= 0.3.4'
38
38
 
39
39
  # for options
@@ -44,5 +44,5 @@ Gem::Specification.new do |spec|
44
44
  spec.add_development_dependency 'rspec', '~> 3.0'
45
45
  spec.add_development_dependency 'timecop'
46
46
  spec.add_development_dependency 'webmock', '>= 3.11.1'
47
-
47
+ spec.add_development_dependency 'parallel', '>= 1.20.1'
48
48
  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'
@@ -73,7 +73,8 @@ module Bitcoin
73
73
  # for script
74
74
 
75
75
  # witness version
76
- WITNESS_VERSION = 0x00
76
+ WITNESS_VERSION_V0 = 0x00
77
+ WITNESS_VERSION_V1 = Bitcoin::Opcodes::OP_1
77
78
 
78
79
  # Maximum script length in bytes
79
80
  MAX_SCRIPT_SIZE = 10000
data/lib/bitcoin/ext.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  module Bitcoin
2
2
  module Ext
3
3
  autoload :JsonParser, 'bitcoin/ext/json_parser'
4
+ autoload :ArrayExt, 'bitcoin/ext/array_ext'
4
5
  end
5
6
  end
@@ -0,0 +1,22 @@
1
+ module Bitcoin
2
+ module Ext
3
+ module ArrayExt
4
+
5
+ refine Array do
6
+
7
+ # resize array content with +initial_value+.
8
+ # expect to behave like vec#resize in c++.
9
+ def resize!(new_size, initial_value = 0)
10
+ if size < new_size
11
+ (new_size - size).times{self.<< initial_value}
12
+ elsif size > new_size
13
+ (size - new_size).times{delete_at(-1)}
14
+ end
15
+ self
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -3,6 +3,11 @@ class ::ECDSA::Signature
3
3
  def to_der
4
4
  ECDSA::Format::SignatureDerString.encode(self)
5
5
  end
6
+
7
+ def ==(other)
8
+ return false unless other.is_a?(ECDSA::Signature)
9
+ r == other.r && s == other.s
10
+ end
6
11
  end
7
12
 
8
13
  class ::ECDSA::Point
@@ -280,7 +280,7 @@ module Bitcoin
280
280
  l = Bitcoin.hmac_sha512(chain_code, data)
281
281
  left = l[0..31].bth.to_i(16)
282
282
  raise 'invalid key' if left >= CURVE_ORDER
283
- p1 = Bitcoin::Secp256k1::GROUP.generator.multiply_by_scalar(left)
283
+ p1 = Bitcoin::Key.new(priv_key: left.to_s(16), key_type: Bitcoin::Key::TYPES[:uncompressed]).to_point
284
284
  p2 = Bitcoin::Key.new(pubkey: pubkey, key_type: key_type).to_point
285
285
  new_key.pubkey = (p1 + p2).to_hex
286
286
  new_key.chain_code = l[32..-1]
data/lib/bitcoin/key.rb CHANGED
@@ -10,6 +10,7 @@ module Bitcoin
10
10
  COMPRESSED_PUBLIC_KEY_SIZE = 33
11
11
  SIGNATURE_SIZE = 72
12
12
  COMPACT_SIGNATURE_SIZE = 65
13
+ COMPACT_SIG_HEADER_BYTE = 0x1b
13
14
 
14
15
  attr_accessor :priv_key
15
16
  attr_accessor :pubkey
@@ -77,6 +78,23 @@ module Bitcoin
77
78
  new(priv_key: data.bth, key_type: key_type)
78
79
  end
79
80
 
81
+ # Generate from xonly public key.
82
+ # @param [String] xonly_pubkey xonly public key with hex format.
83
+ # @return [Bitcoin::Key] key object has public key.
84
+ def self.from_xonly_pubkey(xonly_pubkey)
85
+ raise ArgumentError, 'xonly_pubkey must be 32 bytes' unless xonly_pubkey.htb.bytesize == 32
86
+ Bitcoin::Key.new(pubkey: "02#{xonly_pubkey}", key_type: TYPES[:compressed])
87
+ end
88
+
89
+ # Generate from public key point.
90
+ # @param [ECDSA::Point] point Public key point.
91
+ # @param [Boolean] compressed whether compressed or not.
92
+ # @return [Bitcoin::Key]
93
+ def self.from_point(point, compressed: true)
94
+ pubkey = ECDSA::Format::PointOctetString.encode(point, compression: compressed).bth
95
+ Bitcoin::Key.new(pubkey: pubkey, key_type: TYPES[:compressed])
96
+ end
97
+
80
98
  # export private key with wif format
81
99
  def to_wif
82
100
  version = Bitcoin.chain_params.privkey_version
@@ -112,6 +130,29 @@ module Bitcoin
112
130
  end
113
131
  end
114
132
 
133
+ # Sign compact signature.
134
+ # @param [String] data message digest to be signed.
135
+ # @return [String] compact signature with binary format.
136
+ def sign_compact(data)
137
+ signature, rec = secp256k1_module.sign_compact(data, priv_key)
138
+ rec = Bitcoin::Key::COMPACT_SIG_HEADER_BYTE + rec + (compressed? ? 4 : 0)
139
+ [rec].pack('C') + ECDSA::Format::IntegerOctetString.encode(signature.r, 32) +
140
+ ECDSA::Format::IntegerOctetString.encode(signature.s, 32)
141
+ end
142
+
143
+ # Recover public key from compact signature.
144
+ # @param [String] data message digest using signature.
145
+ # @param [String] signature signature with binary format.
146
+ # @return [Bitcoin::Key] Recovered public key.
147
+ def self.recover_compact(data, signature)
148
+ rec_id = signature.unpack1('C')
149
+ rec = rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE
150
+ raise ArgumentError, 'Invalid signature parameter' if rec < 0 || rec > 15
151
+ rec = rec & 3
152
+ compressed = (rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE) & 4 != 0
153
+ Bitcoin.secp_impl.recover_compact(data, signature, rec, compressed)
154
+ end
155
+
115
156
  # verify signature using public key
116
157
  # @param [String] sig signature data with binary format
117
158
  # @param [String] data original message
@@ -165,13 +206,14 @@ module Bitcoin
165
206
  # @return [ECDSA::Point]
166
207
  def to_point
167
208
  p = pubkey
168
- p ||= generate_pubkey(priv_key, compressed: compressed)
209
+ p ||= generate_pubkey(priv_key, compressed: compressed?)
169
210
  ECDSA::Format::PointOctetString.decode(p.htb, Bitcoin::Secp256k1::GROUP)
170
211
  end
171
212
 
172
213
  # get xonly public key (32 bytes).
173
214
  # @return [String] xonly public key with hex format
174
215
  def xonly_pubkey
216
+ puts "Derive a public key whose y-coordinate is different from this public key." if compressed? && pubkey[0...2] != '02'
175
217
  pubkey[2..65]
176
218
  end
177
219
 
@@ -16,7 +16,7 @@ module Bitcoin
16
16
  end
17
17
 
18
18
  def self.parse_from_payload(payload)
19
- tx = Bitcoin::Tx.parse_from_payload(payload)
19
+ tx = Bitcoin::Tx.parse_from_payload(payload, strict: true)
20
20
  new(tx, tx.witness?)
21
21
  end
22
22
 
@@ -0,0 +1,47 @@
1
+ module Bitcoin
2
+
3
+ module MessageSign
4
+
5
+ class Error < StandardError; end
6
+
7
+ module_function
8
+
9
+ # Sign a message.
10
+ # @param [Bitcoin::Key] key Private key to sign with.
11
+ # @param [String] message The message to sign.
12
+ # @return [String] Signature, base64 encoded.
13
+ def sign_message(key, message, prefix: Bitcoin.chain_params.message_magic)
14
+ digest = message_hash(message, prefix: prefix)
15
+ compact_sig = key.sign_compact(digest)
16
+ Base64.strict_encode64(compact_sig)
17
+ end
18
+
19
+ # Verify a signed message.
20
+ # @param [String] address Signer's bitcoin address, it must refer to a public key.
21
+ # @param [String] signature The signature in base64 format.
22
+ # @param [String] message The message that was signed.
23
+ # @return [Boolean] Verification result.
24
+ def verify_message(address, signature, message, prefix: Bitcoin.chain_params.message_magic)
25
+ validate_address!(address)
26
+ sig = Base64.decode64(signature)
27
+ raise ArgumentError, 'Invalid signature length' unless sig.bytesize == Bitcoin::Key::COMPACT_SIGNATURE_SIZE
28
+ digest = message_hash(message, prefix: prefix)
29
+ pubkey = Bitcoin::Key.recover_compact(digest, sig)
30
+ return false unless pubkey
31
+ pubkey.to_p2pkh == address
32
+ end
33
+
34
+ # Hashes a message for signing and verification.
35
+ def message_hash(message, prefix: Bitcoin.chain_params.message_magic)
36
+ Bitcoin.double_sha256(Bitcoin.pack_var_string(prefix) << Bitcoin.pack_var_string(message))
37
+ end
38
+
39
+ def validate_address!(address)
40
+ raise ArgumentError, 'Invalid address' unless Bitcoin.valid_address?(address)
41
+ script = Bitcoin::Script.parse_from_addr(address)
42
+ raise ArgumentError, 'Address has no key' unless script.p2pkh?
43
+ end
44
+
45
+ private_class_method :validate_address!
46
+ end
47
+ end
@@ -17,7 +17,7 @@ module Bitcoin
17
17
  end
18
18
 
19
19
  def transactions
20
- @values[:transactions].map{|raw_tx|Bitcoin::Tx.parse_from_payload(raw_tx)}
20
+ @values[:transactions].map{|raw_tx|Bitcoin::Tx.parse_from_payload(raw_tx, strict: true)}
21
21
  end
22
22
 
23
23
  end
@@ -44,7 +44,7 @@ module Bitcoin
44
44
  when PSBT_IN_TYPES[:non_witness_utxo]
45
45
  raise ArgumentError, 'Invalid non-witness utxo typed key.' unless key_len == 1
46
46
  raise ArgumentError, 'Duplicate Key, input non-witness utxo already provided.' if input.non_witness_utxo
47
- input.non_witness_utxo = Bitcoin::Tx.parse_from_payload(value)
47
+ input.non_witness_utxo = Bitcoin::Tx.parse_from_payload(value, strict: true)
48
48
  when PSBT_IN_TYPES[:witness_utxo]
49
49
  raise ArgumentError, 'Invalid input witness utxo typed key.' unless key_len == 1
50
50
  raise ArgumentError, 'Duplicate Key, input witness utxo already provided.' if input.witness_utxo
@@ -74,7 +74,7 @@ module Bitcoin
74
74
  when PSBT_GLOBAL_TYPES[:unsigned_tx]
75
75
  raise ArgumentError, 'Invalid global transaction typed key.' unless key_len == 1
76
76
  raise ArgumentError, 'Duplicate Key, unsigned tx already provided.' if partial_tx.tx
77
- partial_tx.tx = Bitcoin::Tx.parse_from_payload(value, non_witness: true)
77
+ partial_tx.tx = Bitcoin::Tx.parse_from_payload(value, non_witness: true, strict: true)
78
78
  partial_tx.tx.in.each do |tx_in|
79
79
  raise ArgumentError, 'Unsigned tx does not have empty scriptSigs and scriptWitnesses.' if !tx_in.script_sig.empty? || !tx_in.script_witness.empty?
80
80
  end
@@ -75,7 +75,7 @@ module Bitcoin
75
75
 
76
76
  # broadcast transaction
77
77
  def sendrawtransaction(hex_tx)
78
- tx = Bitcoin::Tx.parse_from_payload(hex_tx.htb)
78
+ tx = Bitcoin::Tx.parse_from_payload(hex_tx.htb, strict: true)
79
79
  # TODO check wether tx is valid
80
80
  node.broadcast(tx)
81
81
  tx.txid
@@ -84,7 +84,7 @@ module Bitcoin
84
84
  # decode tx data.
85
85
  def decoderawtransaction(hex_tx)
86
86
  begin
87
- Bitcoin::Tx.parse_from_payload(hex_tx.htb).to_h
87
+ Bitcoin::Tx.parse_from_payload(hex_tx.htb, strict: true ).to_h
88
88
  rescue Exception
89
89
  raise ArgumentError.new('TX decode failed')
90
90
  end
@@ -21,7 +21,7 @@ module Bitcoin
21
21
 
22
22
  # generate P2WPKH script
23
23
  def self.to_p2wpkh(pubkey_hash)
24
- new << WITNESS_VERSION << pubkey_hash
24
+ new << WITNESS_VERSION_V0 << pubkey_hash
25
25
  end
26
26
 
27
27
  # generate m of n multisig p2sh script
@@ -52,7 +52,7 @@ module Bitcoin
52
52
  end
53
53
 
54
54
  # generate m of n multisig script
55
- # @param [String] m the number of signatures required for multisig
55
+ # @param [Integer] m the number of signatures required for multisig
56
56
  # @param [Array] pubkeys array of public keys that compose multisig
57
57
  # @return [Script] multisig script.
58
58
  def self.to_multisig_script(m, pubkeys, sort: false)
@@ -64,7 +64,7 @@ module Bitcoin
64
64
  # @param [Script] redeem_script target redeem script
65
65
  # @param [Script] p2wsh script
66
66
  def self.to_p2wsh(redeem_script)
67
- new << WITNESS_VERSION << redeem_script.to_sha256
67
+ new << WITNESS_VERSION_V0 << redeem_script.to_sha256
68
68
  end
69
69
 
70
70
  # generate script from string.
@@ -87,17 +87,21 @@ module Bitcoin
87
87
  def self.parse_from_addr(addr)
88
88
  begin
89
89
  segwit_addr = Bech32::SegwitAddr.new(addr)
90
- raise 'Invalid hrp.' unless Bitcoin.chain_params.bech32_hrp == segwit_addr.hrp
90
+ raise ArgumentError, 'Invalid address.' unless Bitcoin.chain_params.bech32_hrp == segwit_addr.hrp
91
91
  Bitcoin::Script.parse_from_payload(segwit_addr.to_script_pubkey.htb)
92
92
  rescue Exception => e
93
+ begin
93
94
  hex, addr_version = Bitcoin.decode_base58_address(addr)
95
+ rescue
96
+ raise ArgumentError, 'Invalid address.'
97
+ end
94
98
  case addr_version
95
99
  when Bitcoin.chain_params.address_version
96
100
  Bitcoin::Script.to_p2pkh(hex)
97
101
  when Bitcoin.chain_params.p2sh_version
98
102
  Bitcoin::Script.to_p2sh(hex)
99
103
  else
100
- throw e
104
+ raise ArgumentError, 'Invalid address.'
101
105
  end
102
106
  end
103
107
  end
@@ -145,7 +149,7 @@ module Bitcoin
145
149
  end
146
150
 
147
151
  # Output script payload.
148
- # @param [Boolean] length_prefixed Flag whether the length of the pyrode should be given at the beginning.(default: false)
152
+ # @param [Boolean] length_prefixed Flag whether the length of the payload should be given at the beginning.(default: false)
149
153
  # @return [String] payload
150
154
  def to_payload(length_prefixed = false)
151
155
  p = chunks.join
@@ -177,27 +181,40 @@ module Bitcoin
177
181
 
178
182
  # check whether standard script.
179
183
  def standard?
180
- p2pkh? | p2sh? | p2wpkh? | p2wsh? | multisig? | standard_op_return?
184
+ p2pkh? | p2sh? | p2wpkh? | p2wsh? | p2tr? | multisig? | standard_op_return?
181
185
  end
182
186
 
183
- # whether this script is a P2PKH format script.
187
+ # Check whether this script is a P2PKH format script.
188
+ # @return [Boolean] if P2PKH return true, otherwise false
184
189
  def p2pkh?
185
190
  return false unless chunks.size == 5
186
191
  [OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG] ==
187
192
  (chunks[0..1]+ chunks[3..4]).map(&:ord) && chunks[2].bytesize == 21
188
193
  end
189
194
 
190
- # whether this script is a P2WPKH format script.
195
+ # Check whether this script is a P2WPKH format script.
196
+ # @return [Boolean] if P2WPKH return true, otherwise false
191
197
  def p2wpkh?
192
198
  return false unless chunks.size == 2
193
- chunks[0].ord == WITNESS_VERSION && chunks[1].bytesize == 21
199
+ chunks[0].ord == WITNESS_VERSION_V0 && chunks[1].bytesize == 21
194
200
  end
195
201
 
202
+ # Check whether this script is a P2WPSH format script.
203
+ # @return [Boolean] if P2WPSH return true, otherwise false
196
204
  def p2wsh?
197
205
  return false unless chunks.size == 2
198
- chunks[0].ord == WITNESS_VERSION && chunks[1].bytesize == 33
206
+ chunks[0].ord == WITNESS_VERSION_V0 && chunks[1].bytesize == 33
207
+ end
208
+
209
+ # Check whether this script is a P2TR format script.
210
+ # @return [Boolean] if P2TR return true, otherwise false
211
+ def p2tr?
212
+ return false unless chunks.size == 2
213
+ chunks[0].ord == WITNESS_VERSION_V1 && chunks[1].bytesize == 33
199
214
  end
200
215
 
216
+ # Check whether this script is a P2SH format script.
217
+ # @return [Boolean] if P2SH return true, otherwise false
201
218
  def p2sh?
202
219
  return false unless chunks.size == 3
203
220
  OP_HASH160 == chunks[0].ord && OP_EQUAL == chunks[2].ord && chunks[1].bytesize == 21
@@ -498,7 +515,7 @@ module Bitcoin
498
515
  end
499
516
 
500
517
  def ==(other)
501
- return false unless other
518
+ return false unless other.is_a?(Script)
502
519
  chunks == other.chunks
503
520
  end
504
521
 
@@ -3,6 +3,7 @@ module Bitcoin
3
3
  class ScriptInterpreter
4
4
 
5
5
  include Bitcoin::Opcodes
6
+ using Bitcoin::Ext::ArrayExt
6
7
 
7
8
  attr_reader :stack
8
9
  attr_reader :debug
@@ -55,6 +56,8 @@ module Bitcoin
55
56
  version, program = script_pubkey.witness_data
56
57
  stack_copy = stack.dup
57
58
  return false unless verify_witness_program(witness, version, program, false)
59
+ # Bypass the cleanstack check at the end. The actual stack is obviously not clean for witness programs.
60
+ stack.resize!(1, Script.encode_number(0))
58
61
  end
59
62
 
60
63
  # Additional validation for spend-to-script-hash transactions
@@ -697,7 +700,7 @@ module Bitcoin
697
700
  begin
698
701
  path_len = (control.bytesize - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE
699
702
  xonly_pubkey = control[1...TAPROOT_CONTROL_BASE_SIZE]
700
- p = Bitcoin::Key.new(pubkey: "02#{xonly_pubkey.bth}", key_type: Key::TYPES[:compressed])
703
+ p = Bitcoin::Key.from_xonly_pubkey(xonly_pubkey.bth)
701
704
  k = leaf_hash
702
705
  path_len.times do |i|
703
706
  pos = (TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * i)
@@ -56,11 +56,9 @@ module Bitcoin
56
56
 
57
57
  return set_error(SCRIPT_ERR_SCHNORR_SIG_HASHTYPE) unless (hash_type <= 0x03 || (hash_type >= 0x81 && hash_type <= 0x83))
58
58
 
59
- opts[:prevouts] = prevouts
60
-
61
59
  begin
62
- sighash = tx.sighash_for_input(input_index, opts: opts, hash_type: hash_type, sig_version: sig_version)
63
- key = Key.new(pubkey: "02#{pubkey}", key_type: Key::TYPES[:compressed])
60
+ sighash = tx.sighash_for_input(input_index, opts: opts, hash_type: hash_type, sig_version: sig_version, prevouts: prevouts)
61
+ key = Key.from_xonly_pubkey(pubkey)
64
62
  key.verify(sig, sighash, algo: :schnorr)
65
63
  rescue ArgumentError
66
64
  return set_error(SCRIPT_ERR_SCHNORR_SIG_HASHTYPE)
@@ -4,8 +4,8 @@
4
4
  module Bitcoin
5
5
  module Secp256k1
6
6
 
7
- # binding for secp256k1 (https://github.com/bitcoin/bitcoin/tree/v0.14.2/src/secp256k1)
8
- # tag: v0.14.2
7
+ # binding for secp256k1 (https://github.com/bitcoin-core/secp256k1/)
8
+ # commit: efad3506a8937162e8010f5839fdf3771dfcf516
9
9
  # this is not included by default, to enable set shared object path to ENV['SECP256K1_LIB_PATH']
10
10
  # for linux, ENV['SECP256K1_LIB_PATH'] = '/usr/local/lib/libsecp256k1.so'
11
11
  # for mac,
@@ -54,6 +54,10 @@ module Bitcoin
54
54
  attach_function(:secp256k1_schnorrsig_verify, [:pointer, :pointer, :pointer, :pointer], :int)
55
55
  attach_function(:secp256k1_keypair_create, [:pointer, :pointer, :pointer], :int)
56
56
  attach_function(:secp256k1_xonly_pubkey_parse, [:pointer, :pointer, :pointer], :int)
57
+ attach_function(:secp256k1_ecdsa_sign_recoverable, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
58
+ attach_function(:secp256k1_ecdsa_recoverable_signature_serialize_compact, [:pointer, :pointer, :pointer, :pointer], :int)
59
+ attach_function(:secp256k1_ecdsa_recover, [:pointer, :pointer, :pointer, :pointer], :int)
60
+ attach_function(:secp256k1_ecdsa_recoverable_signature_parse_compact, [:pointer, :pointer, :pointer, :int], :int)
57
61
  end
58
62
 
59
63
  def with_context(flags: (SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN))
@@ -116,6 +120,52 @@ module Bitcoin
116
120
  end
117
121
  end
118
122
 
123
+ # Sign data with compact format.
124
+ # @param [String] data a data to be signed with binary format
125
+ # @param [String] privkey a private key using sign with hex format
126
+ # @return [Array[signature, recovery id]]
127
+ def sign_compact(data, privkey)
128
+ with_context do |context|
129
+ sig = FFI::MemoryPointer.new(:uchar, 65)
130
+ hash =FFI::MemoryPointer.new(:uchar, data.bytesize).put_bytes(0, data)
131
+ priv_key = privkey.htb
132
+ sec_key = FFI::MemoryPointer.new(:uchar, priv_key.bytesize).put_bytes(0, priv_key)
133
+ result = secp256k1_ecdsa_sign_recoverable(context, sig, hash, sec_key, nil, nil)
134
+ raise 'secp256k1_ecdsa_sign_recoverable failed.' if result == 0
135
+
136
+ output = FFI::MemoryPointer.new(:uchar, 64)
137
+ rec = FFI::MemoryPointer.new(:uint64)
138
+ result = secp256k1_ecdsa_recoverable_signature_serialize_compact(context, output, rec, sig)
139
+ raise 'secp256k1_ecdsa_recoverable_signature_serialize_compact failed.' unless result == 1
140
+
141
+ raw_sig = output.read_string(64)
142
+ [ECDSA::Signature.new(raw_sig[0...32].bti, raw_sig[32..-1].bti), rec.read(:int)]
143
+ end
144
+ end
145
+
146
+ # Recover public key from compact signature.
147
+ # @param [String] data message digest using signature.
148
+ # @param [String] signature signature with binary format.
149
+ # @param [Integer] rec recovery id.
150
+ # @param [Boolean] compressed whether compressed public key or not.
151
+ # @return [Bitcoin::Key] Recovered public key.
152
+ def recover_compact(data, signature, rec, compressed)
153
+ with_context do |context|
154
+ sig = FFI::MemoryPointer.new(:uchar, 65)
155
+ input = FFI::MemoryPointer.new(:uchar, 64).put_bytes(0, signature[1..-1])
156
+ result = secp256k1_ecdsa_recoverable_signature_parse_compact(context, sig, input, rec)
157
+ raise 'secp256k1_ecdsa_recoverable_signature_parse_compact failed.' unless result == 1
158
+
159
+ pubkey = FFI::MemoryPointer.new(:uchar, 64)
160
+ msg = FFI::MemoryPointer.new(:uchar, data.bytesize).put_bytes(0, data)
161
+ result = secp256k1_ecdsa_recover(context, pubkey, sig, msg)
162
+ raise 'secp256k1_ecdsa_recover failed.' unless result == 1
163
+
164
+ pubkey = serialize_pubkey_internal(context, pubkey.read_string(64), compressed)
165
+ Bitcoin::Key.new(pubkey: pubkey, compressed: compressed)
166
+ end
167
+ end
168
+
119
169
  # verify signature
120
170
  # @param [String] data a data with binary format.
121
171
  # @param [String] sig signature data with binary format
@@ -194,18 +244,7 @@ module Bitcoin
194
244
  internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
195
245
  result = secp256k1_ec_pubkey_create(context, internal_pubkey, privkey.htb)
196
246
  raise 'error creating pubkey' unless result
197
-
198
- pubkey = FFI::MemoryPointer.new(:uchar, 65)
199
- pubkey_len = FFI::MemoryPointer.new(:uint64)
200
- result = if compressed
201
- pubkey_len.put_uint64(0, 33)
202
- secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_COMPRESSED)
203
- else
204
- pubkey_len.put_uint64(0, 65)
205
- secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_UNCOMPRESSED)
206
- end
207
- raise 'error serialize pubkey' unless result || pubkey_len.read_uint64 > 0
208
- pubkey.read_string(pubkey_len.read_uint64).bth
247
+ serialize_pubkey_internal(context, internal_pubkey, compressed)
209
248
  end
210
249
 
211
250
  def sign_ecdsa(data, privkey, extra_entropy)
@@ -282,6 +321,21 @@ module Bitcoin
282
321
  end
283
322
  end
284
323
 
324
+ # Serialize public key.
325
+ def serialize_pubkey_internal(context, pubkey_input, compressed)
326
+ pubkey = FFI::MemoryPointer.new(:uchar, 65)
327
+ pubkey_len = FFI::MemoryPointer.new(:uint64)
328
+ result = if compressed
329
+ pubkey_len.put_uint64(0, 33)
330
+ secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, pubkey_input, SECP256K1_EC_COMPRESSED)
331
+ else
332
+ pubkey_len.put_uint64(0, 65)
333
+ secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, pubkey_input, SECP256K1_EC_UNCOMPRESSED)
334
+ end
335
+ raise 'error serialize pubkey' unless result || pubkey_len.read_uint64 > 0
336
+ pubkey.read_string(pubkey_len.read_uint64).bth
337
+ end
338
+
285
339
  end
286
340
  end
287
341
  end
@@ -43,14 +43,14 @@ module Bitcoin
43
43
 
44
44
  # sign data.
45
45
  # @param [String] data a data to be signed with binary format
46
- # @param [String] privkey a private key using sign
46
+ # @param [String] privkey a private key using sign with hex format
47
47
  # @param [String] extra_entropy a extra entropy with binary format for rfc6979
48
48
  # @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
49
49
  # @return [String] signature data with binary format
50
50
  def sign_data(data, privkey, extra_entropy = nil, algo: :ecdsa)
51
51
  case algo
52
52
  when :ecdsa
53
- sign_ecdsa(data, privkey, extra_entropy)
53
+ sign_ecdsa(data, privkey, extra_entropy)&.first
54
54
  when :schnorr
55
55
  sign_schnorr(data, privkey, extra_entropy)
56
56
  else
@@ -58,6 +58,31 @@ module Bitcoin
58
58
  end
59
59
  end
60
60
 
61
+ # Sign data with compact format.
62
+ # @param [String] data a data to be signed with binary format
63
+ # @param [String] privkey a private key using sign with hex format
64
+ # @return [Array[signature, recovery id]]
65
+ def sign_compact(data, privkey)
66
+ sig, rec = sign_ecdsa(data, privkey, nil)
67
+ [ECDSA::Format::SignatureDerString.decode(sig), rec]
68
+ end
69
+
70
+ # Recover public key from compact signature.
71
+ # @param [String] data message digest using signature.
72
+ # @param [String] signature signature with binary format.
73
+ # @param [Integer] rec recovery id.
74
+ # @param [Boolean] compressed whether compressed public key or not.
75
+ # @return [Bitcoin::Key] Recovered public key.
76
+ def recover_compact(data, signature, rec, compressed)
77
+ r = ECDSA::Format::IntegerOctetString.decode(signature[1...33])
78
+ s = ECDSA::Format::IntegerOctetString.decode(signature[33..-1])
79
+ ECDSA.recover_public_key(Bitcoin::Secp256k1::GROUP, data, ECDSA::Signature.new(r, s)).each do |p|
80
+ if p.y & 1 == rec
81
+ return Bitcoin::Key.from_point(p, compressed: compressed)
82
+ end
83
+ end
84
+ end
85
+
61
86
  # verify signature using public key
62
87
  # @param [String] data a SHA-256 message digest with binary format
63
88
  # @param [String] sig a signature for +data+ with binary format
@@ -113,11 +138,14 @@ module Bitcoin
113
138
  r = point_field.mod(r_point.x)
114
139
  return nil if r.zero?
115
140
 
141
+ rec = r_point.y & 1
142
+
116
143
  e = ECDSA.normalize_digest(data, GROUP.bit_length)
117
144
  s = point_field.mod(point_field.inverse(nonce) * (e + r * private_key))
118
145
 
119
146
  if s > (GROUP.order / 2) # convert low-s
120
147
  s = GROUP.order - s
148
+ rec ^= 1
121
149
  end
122
150
 
123
151
  return nil if s.zero?
@@ -125,7 +153,7 @@ module Bitcoin
125
153
  signature = ECDSA::Signature.new(r, s).to_der
126
154
  public_key = Bitcoin::Key.new(priv_key: privkey.bth).pubkey
127
155
  raise 'Creation of signature failed.' unless Bitcoin::Secp256k1::Ruby.verify_sig(data, signature, public_key)
128
- signature
156
+ [signature, rec]
129
157
  end
130
158
 
131
159
  def sign_schnorr(data, privkey, aux_rand)
@@ -0,0 +1,9 @@
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
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ module Bitcoin
2
+ module Taproot
3
+ class LeafNode
4
+
5
+ attr_reader :script, :leaf_ver
6
+
7
+ # Initialize
8
+ # @param [Bitcoin::Script] script Locking script
9
+ # @param [Integer] leaf_ver The leaf version of this script.
10
+ def initialize(script, leaf_ver)
11
+ raise Taproot::Error, 'script must be Bitcoin::Script object' unless script.is_a?(Bitcoin::Script)
12
+ @script = script
13
+ @leaf_ver = leaf_ver
14
+ end
15
+
16
+ # Calculate leaf hash.
17
+ # @return [String] leaf hash.
18
+ def leaf_hash
19
+ @hash_value ||= Bitcoin.tagged_hash('TapLeaf', [leaf_ver].pack('C') + Bitcoin.pack_var_string(script.to_payload))
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,139 @@
1
+ module Bitcoin
2
+ module Taproot
3
+
4
+ # Utility class to construct Taproot outputs from internal key and script tree.
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 :leaves # Array[LeafNode]
12
+
13
+ # Initialize builder.
14
+ # @param [String] internal_key Internal public key with hex format.
15
+ # @param [Array[Bitcoin::Script]] scripts Scripts for each lock condition.
16
+ # @param [Integer] leaf_ver (Optional) The leaf version of tapscript.
17
+ # @raise [Bitcoin::Taproot::Builder] +internal_pubkey+ dose not xonly public key or script in +scripts+ does not instance of Bitcoin::Script.
18
+ # @return [Bitcoin::Taproot::SimpleBuilder]
19
+ def initialize(internal_key, *scripts, leaf_ver: Bitcoin::TAPROOT_LEAF_TAPSCRIPT)
20
+ raise Error, 'Internal public key must be 32 bytes' unless internal_key.htb.bytesize == 32
21
+ @leaves = scripts.map { |script| LeafNode.new(script, leaf_ver) }
22
+ @internal_key = internal_key
23
+ end
24
+
25
+ # Add lock script to leaf node.
26
+ # @param [Bitcoin::Script] script lock script.
27
+ # @param [Integer] leaf_ver (Optional) The leaf version of tapscript.
28
+ # @raise [Bitcoin::Taproot::Builder] If +script+ does not instance of Bitcoin::Script.
29
+ # @return [Bitcoin::Taproot::SimpleBuilder] self
30
+ def <<(script, leaf_ver: Bitcoin::TAPROOT_LEAF_TAPSCRIPT)
31
+ leaves << LeafNode.new(script, leaf_ver)
32
+ self
33
+ end
34
+
35
+ # Build P2TR script.
36
+ # @return [Bitcoin::Script] P2TR script.
37
+ def build
38
+ q = tweak_public_key
39
+ Bitcoin::Script.new << OP_1 << q.xonly_pubkey
40
+ end
41
+
42
+ # Compute the tweaked public key.
43
+ # @return [Bitcoin::Key] the tweaked public key
44
+ def tweak_public_key
45
+ key = Bitcoin::Key.new(priv_key: tweak.bth, key_type: Key::TYPES[:compressed])
46
+ Bitcoin::Key.from_point(key.to_point + Bitcoin::Key.from_xonly_pubkey(internal_key).to_point)
47
+ end
48
+
49
+ # Compute the secret key for a tweaked public key.
50
+ # @param [Bitcoin::Key] key key object contains private key.
51
+ # @return [Bitcoin::Key] secret key for a tweaked public key
52
+ def tweak_private_key(key)
53
+ raise Error, 'Requires private key' unless key.priv_key
54
+ p = key.to_point
55
+ private_key = p.has_even_y? ? key.priv_key.to_i(16) : ECDSA::Group::Secp256k1.order - key.priv_key.to_i(16)
56
+ Bitcoin::Key.new(priv_key: ((tweak.bti + private_key) % ECDSA::Group::Secp256k1.order).to_even_length_hex)
57
+ end
58
+
59
+ # Generate control block needed to unlock with script-path.
60
+ # @param [Bitcoin::Script] script Script to use for unlocking.
61
+ # @param [Integer] leaf_ver leaf version of script.
62
+ # @return [String] control block with binary format.
63
+ def control_block(script, leaf_ver: Bitcoin::TAPROOT_LEAF_TAPSCRIPT)
64
+ path = inclusion_proof(script, leaf_ver: leaf_ver)
65
+ parity = tweak_public_key.to_point.has_even_y? ? 0 : 1
66
+ [parity + leaf_ver].pack("C") + internal_key.htb + path.join
67
+ end
68
+
69
+ # Generate inclusion proof for +script+.
70
+ # @param [Bitcoin::Script] script The script in script tree.
71
+ # @param [Integer] leaf_ver (Optional) The leaf version of tapscript.
72
+ # @return [Array[String]] Inclusion proof.
73
+ def inclusion_proof(script, leaf_ver: Bitcoin::TAPROOT_LEAF_TAPSCRIPT)
74
+ parents = leaves
75
+ parent_hash = leaf_hash(script, leaf_ver: leaf_ver)
76
+ proofs = []
77
+ until parents.size == 1
78
+ parents = parents.each_slice(2).map do |pair|
79
+ combined = combine_hash(pair)
80
+ unless pair.size == 1
81
+ if hash_value(pair[0]) == parent_hash
82
+ proofs << hash_value(pair[1])
83
+ parent_hash = combined
84
+ elsif hash_value(pair[1]) == parent_hash
85
+ proofs << hash_value(pair[0])
86
+ parent_hash = combined
87
+ end
88
+ end
89
+ combined
90
+ end
91
+ end
92
+ proofs
93
+ end
94
+
95
+ # Computes leaf hash
96
+ # @param [Bitcoin::Script] script
97
+ # @param [Integer] leaf_ver leaf version
98
+ # @@return [String] leaf hash with binary format.
99
+ def leaf_hash(script, leaf_ver: Bitcoin::TAPROOT_LEAF_TAPSCRIPT)
100
+ raise Error, 'script does not exist' unless leaves.find{ |leaf| leaf.script == script}
101
+ LeafNode.new(script, leaf_ver).leaf_hash
102
+ end
103
+
104
+ private
105
+
106
+ # Compute tweak from script tree.
107
+ # @return [String] tweak with binary format.
108
+ def tweak
109
+ parents = leaves
110
+ if parents.empty?
111
+ parents = ['']
112
+ else
113
+ parents = parents.each_slice(2).map { |pair| combine_hash(pair) } until parents.size == 1
114
+ end
115
+ t = Bitcoin.tagged_hash('TapTweak', internal_key.htb + parents.first)
116
+ raise Error, 'tweak value exceeds the curve order' if t.bti >= ECDSA::Group::Secp256k1.order
117
+ t
118
+ end
119
+
120
+ def combine_hash(pair)
121
+ if pair.size == 1
122
+ hash_value(pair[0])
123
+ else
124
+ hash1 = hash_value(pair[0])
125
+ hash2 = hash_value(pair[1])
126
+
127
+ # Lexicographically sort a and b's hash, and compute parent hash.
128
+ payload = hash1.bth < hash2.bth ? hash1 + hash2 : hash2 + hash1
129
+ Bitcoin.tagged_hash('TapBranch', payload)
130
+ end
131
+ end
132
+
133
+ def hash_value(leaf_or_branch)
134
+ leaf_or_branch.is_a?(LeafNode) ? leaf_or_branch.leaf_hash : leaf_or_branch
135
+ end
136
+ end
137
+ end
138
+
139
+ 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.7.0"
2
+ VERSION = "0.8.0"
3
3
  end
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.7.0
4
+ version: 0.8.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-02-19 00:00:00.000000000 Z
11
+ date: 2021-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ecdsa
@@ -198,14 +198,14 @@ 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
209
  - !ruby/object:Gem::Dependency
210
210
  name: base32
211
211
  requirement: !ruby/object:Gem::Requirement
@@ -304,6 +304,20 @@ dependencies:
304
304
  - - ">="
305
305
  - !ruby/object:Gem::Version
306
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
307
321
  description: The implementation of Bitcoin Protocol for Ruby.
308
322
  email:
309
323
  - azuchi@chaintope.com
@@ -313,11 +327,12 @@ executables:
313
327
  extensions: []
314
328
  extra_rdoc_files: []
315
329
  files:
330
+ - ".github/workflows/ruby.yml"
316
331
  - ".gitignore"
317
332
  - ".rspec"
333
+ - ".rspec_parallel"
318
334
  - ".ruby-gemset"
319
335
  - ".ruby-version"
320
- - ".travis.yml"
321
336
  - CODE_OF_CONDUCT.md
322
337
  - Gemfile
323
338
  - LICENSE.txt
@@ -346,6 +361,7 @@ files:
346
361
  - lib/bitcoin/descriptor.rb
347
362
  - lib/bitcoin/errors.rb
348
363
  - lib/bitcoin/ext.rb
364
+ - lib/bitcoin/ext/array_ext.rb
349
365
  - lib/bitcoin/ext/ecdsa.rb
350
366
  - lib/bitcoin/ext/json_parser.rb
351
367
  - lib/bitcoin/ext_key.rb
@@ -400,6 +416,7 @@ files:
400
416
  - lib/bitcoin/message/tx.rb
401
417
  - lib/bitcoin/message/ver_ack.rb
402
418
  - lib/bitcoin/message/version.rb
419
+ - lib/bitcoin/message_sign.rb
403
420
  - lib/bitcoin/mnemonic.rb
404
421
  - lib/bitcoin/mnemonic/wordlist/chinese_simplified.txt
405
422
  - lib/bitcoin/mnemonic/wordlist/chinese_traditional.txt
@@ -459,6 +476,9 @@ files:
459
476
  - lib/bitcoin/store/db/level_db.rb
460
477
  - lib/bitcoin/store/spv_chain.rb
461
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
462
482
  - lib/bitcoin/tx.rb
463
483
  - lib/bitcoin/tx_in.rb
464
484
  - lib/bitcoin/tx_out.rb
data/.travis.yml DELETED
@@ -1,14 +0,0 @@
1
- dist: bionic
2
- language: ruby
3
- rvm:
4
- - 2.4.10
5
- - 2.5.8
6
- - 2.6.6
7
- - 2.7.2
8
- - 3.0.0
9
- addons:
10
- apt:
11
- packages:
12
- - libleveldb-dev
13
- script:
14
- - bundle exec parallel_test spec/ -n 6 --type rspec