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 +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 [![Build Status](https://
|
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.
|
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
|