bitcoinrb 0.7.0 → 0.8.0

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