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 +4 -4
- data/.github/workflows/ruby.yml +37 -0
- data/.rspec_parallel +2 -0
- data/README.md +1 -1
- data/bitcoinrb.gemspec +2 -2
- data/lib/bitcoin.rb +2 -0
- data/lib/bitcoin/constants.rb +2 -1
- data/lib/bitcoin/ext.rb +1 -0
- data/lib/bitcoin/ext/array_ext.rb +22 -0
- data/lib/bitcoin/ext/ecdsa.rb +5 -0
- data/lib/bitcoin/ext_key.rb +1 -1
- data/lib/bitcoin/key.rb +43 -1
- data/lib/bitcoin/message/tx.rb +1 -1
- data/lib/bitcoin/message_sign.rb +47 -0
- data/lib/bitcoin/payments/payment.pb.rb +1 -1
- data/lib/bitcoin/psbt/input.rb +1 -1
- data/lib/bitcoin/psbt/tx.rb +1 -1
- data/lib/bitcoin/rpc/request_handler.rb +2 -2
- data/lib/bitcoin/script/script.rb +29 -12
- data/lib/bitcoin/script/script_interpreter.rb +4 -1
- data/lib/bitcoin/script/tx_checker.rb +2 -4
- data/lib/bitcoin/secp256k1/native.rb +68 -14
- data/lib/bitcoin/secp256k1/ruby.rb +31 -3
- data/lib/bitcoin/taproot.rb +9 -0
- data/lib/bitcoin/taproot/leaf_node.rb +23 -0
- data/lib/bitcoin/taproot/simple_builder.rb +139 -0
- data/lib/bitcoin/tx.rb +17 -16
- data/lib/bitcoin/version.rb +1 -1
- metadata +25 -5
- data/.travis.yml +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: af836e45167490d93d1d9b5d8f29f4e473a78dc314b98cd789bb47e347a42037
|
4
|
+
data.tar.gz: 020fe745057f3b987c8bd8828be9f7f657d3085a7d8ee37cf0218b0761202325
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Bitcoinrb [](https://github.com/chaintope/bitcoinrb/actions/workflows/ruby.yml) [](https://badge.fury.io/rb/bitcoinrb) [](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.
|
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'
|
data/lib/bitcoin/constants.rb
CHANGED
data/lib/bitcoin/ext.rb
CHANGED
@@ -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
|
data/lib/bitcoin/ext/ecdsa.rb
CHANGED
data/lib/bitcoin/ext_key.rb
CHANGED
@@ -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::
|
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
|
|
data/lib/bitcoin/message/tx.rb
CHANGED
@@ -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
|
data/lib/bitcoin/psbt/input.rb
CHANGED
@@ -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
|
data/lib/bitcoin/psbt/tx.rb
CHANGED
@@ -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 <<
|
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 [
|
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 <<
|
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
|
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
|
-
|
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
|
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 ==
|
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 ==
|
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.
|
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.
|
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/
|
8
|
-
#
|
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,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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
data/lib/bitcoin/version.rb
CHANGED
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.
|
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-
|
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.
|
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.
|
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
|