bitcoinrb 1.1.1 → 1.2.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/lib/bitcoin/block_header.rb +5 -1
- data/lib/bitcoin/constants.rb +2 -0
- data/lib/bitcoin/key.rb +49 -4
- data/lib/bitcoin/out_point.rb +9 -0
- data/lib/bitcoin/psbt/input.rb +44 -0
- data/lib/bitcoin/psbt/output.rb +18 -0
- data/lib/bitcoin/psbt.rb +9 -0
- data/lib/bitcoin/script/script.rb +13 -0
- data/lib/bitcoin/script/script_interpreter.rb +11 -21
- data/lib/bitcoin/secp256k1/native.rb +1 -1
- data/lib/bitcoin/secp256k1/ruby.rb +1 -1
- data/lib/bitcoin/taproot/control_block.rb +47 -0
- data/lib/bitcoin/taproot/simple_builder.rb +3 -3
- data/lib/bitcoin/taproot.rb +1 -0
- data/lib/bitcoin/tx_in.rb +1 -2
- data/lib/bitcoin/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cbc1459b87de8addec350661eb84c3a8bd87503eff03d6bad50c329660fc2749
|
4
|
+
data.tar.gz: ccb70c6d406a1b4f7134c7c8b8f13f536ba8197d3479b4fca635c073cccd6f11
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ed8400e197cb53d7232265b87f201174e60dae37952e075dae4e4f961b41e9967a24fa2998845100212747493a0dc2bd0b03a79ca208a2beb824cfd48f4a9bf
|
7
|
+
data.tar.gz: 7585bf0a8179d1144a6f6310a63817538075a111ca92a248c2469df962c2fb913f091abffacdf57096cfb183ebe40406080ce807deca3c052c1a63b1934d895c
|
data/lib/bitcoin/block_header.rb
CHANGED
@@ -21,8 +21,12 @@ module Bitcoin
|
|
21
21
|
@nonce = nonce
|
22
22
|
end
|
23
23
|
|
24
|
+
# Parse block header from payload.
|
25
|
+
# @param [String|StringIO] payload block header paylaod
|
26
|
+
# @return [Bitcoin::BlockHeader]
|
24
27
|
def self.parse_from_payload(payload)
|
25
|
-
|
28
|
+
buf = payload.is_a?(String) ? StringIO.new(payload) : payload
|
29
|
+
version, prev_hash, merkle_root, time, bits, nonce = buf.read(80).unpack('Va32a32VVV')
|
26
30
|
new(version, prev_hash.bth, merkle_root.bth, time, bits, nonce)
|
27
31
|
end
|
28
32
|
|
data/lib/bitcoin/constants.rb
CHANGED
@@ -11,6 +11,8 @@ module Bitcoin
|
|
11
11
|
HASH160_SIZE = 20
|
12
12
|
# Byte size of the HASH256 hash
|
13
13
|
HASH256_SIZE = 32
|
14
|
+
# Byte size of x-only public key
|
15
|
+
X_ONLY_PUBKEY_SIZE = 32
|
14
16
|
|
15
17
|
# The maximum allowed size for a serialized block, in bytes (only for buffer size limits)
|
16
18
|
MAX_BLOCK_SERIALIZED_SIZE = 4_000_000
|
data/lib/bitcoin/key.rb
CHANGED
@@ -17,7 +17,7 @@ module Bitcoin
|
|
17
17
|
attr_accessor :key_type
|
18
18
|
attr_reader :secp256k1_module
|
19
19
|
|
20
|
-
TYPES = {uncompressed: 0x00, compressed: 0x01, p2pkh: 0x10, p2wpkh: 0x11, p2wpkh_p2sh: 0x12}
|
20
|
+
TYPES = {uncompressed: 0x00, compressed: 0x01, p2pkh: 0x10, p2wpkh: 0x11, p2wpkh_p2sh: 0x12, p2tr: 0x13}
|
21
21
|
|
22
22
|
MIN_PRIV_KEY_MOD_ORDER = 0x01
|
23
23
|
# Order of secp256k1's generator minus 1.
|
@@ -81,7 +81,7 @@ module Bitcoin
|
|
81
81
|
# @param [String] xonly_pubkey xonly public key with hex format.
|
82
82
|
# @return [Bitcoin::Key] key object has public key.
|
83
83
|
def self.from_xonly_pubkey(xonly_pubkey)
|
84
|
-
raise ArgumentError,
|
84
|
+
raise ArgumentError, "xonly_pubkey must be #{X_ONLY_PUBKEY_SIZE} bytes" unless xonly_pubkey.htb.bytesize == X_ONLY_PUBKEY_SIZE
|
85
85
|
Bitcoin::Key.new(pubkey: "02#{xonly_pubkey}", key_type: TYPES[:compressed])
|
86
86
|
end
|
87
87
|
|
@@ -180,23 +180,68 @@ module Bitcoin
|
|
180
180
|
end
|
181
181
|
|
182
182
|
# get pay to pubkey hash address
|
183
|
-
# @
|
183
|
+
# @return [String] address
|
184
184
|
def to_p2pkh
|
185
185
|
Bitcoin::Script.to_p2pkh(hash160).to_addr
|
186
186
|
end
|
187
187
|
|
188
188
|
# get pay to witness pubkey hash address
|
189
|
-
# @
|
189
|
+
# @return [String] address
|
190
190
|
def to_p2wpkh
|
191
191
|
Bitcoin::Script.to_p2wpkh(hash160).to_addr
|
192
192
|
end
|
193
193
|
|
194
|
+
# Get pay to taproot address
|
195
|
+
# @return [String] address
|
196
|
+
def to_p2tr
|
197
|
+
Bitcoin::Script.to_p2tr(self).to_addr
|
198
|
+
end
|
199
|
+
|
194
200
|
# get p2wpkh address nested in p2sh.
|
195
201
|
# @deprecated
|
196
202
|
def to_nested_p2wpkh
|
197
203
|
Bitcoin::Script.to_p2wpkh(hash160).to_p2sh.to_addr
|
198
204
|
end
|
199
205
|
|
206
|
+
# Determine if it is a P2PKH from key_type.
|
207
|
+
# @return [Boolean]
|
208
|
+
def p2pkh?
|
209
|
+
[TYPES[:uncompressed], TYPES[:compressed], TYPES[:p2pkh]].include?(key_type)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Determine if it is a P2WPKH from key_type.
|
213
|
+
# @return [Boolean]
|
214
|
+
def p2wpkh?
|
215
|
+
key_type == TYPES[:p2wpkh]
|
216
|
+
end
|
217
|
+
|
218
|
+
# Determine if it is a nested P2WPKH from key_type.
|
219
|
+
# @return [Boolean]
|
220
|
+
def nested_p2wpkh?
|
221
|
+
key_type == TYPES[:p2wpkh_p2sh]
|
222
|
+
end
|
223
|
+
|
224
|
+
# Determine if it is a nested P2TR from key_type.
|
225
|
+
# @return [Boolean]
|
226
|
+
def p2tr?
|
227
|
+
key_type == TYPES[:p2tr]
|
228
|
+
end
|
229
|
+
|
230
|
+
# Returns the address corresponding to key_type.
|
231
|
+
# @return [String] address
|
232
|
+
def to_addr
|
233
|
+
case key_type
|
234
|
+
when TYPES[:uncompressed], TYPES[:compressed], TYPES[:p2pkh]
|
235
|
+
to_p2pkh
|
236
|
+
when TYPES[:p2wpkh]
|
237
|
+
to_p2wpkh
|
238
|
+
when TYPES[:p2wpkh_p2sh]
|
239
|
+
to_nested_p2wpkh
|
240
|
+
when TYPES[:p2tr]
|
241
|
+
to_p2tr
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
200
245
|
def compressed?
|
201
246
|
key_type != TYPES[:uncompressed]
|
202
247
|
end
|
data/lib/bitcoin/out_point.rb
CHANGED
@@ -20,6 +20,15 @@ module Bitcoin
|
|
20
20
|
self.new(txid.rhex, index)
|
21
21
|
end
|
22
22
|
|
23
|
+
# Parse from payload
|
24
|
+
# @param [String|StringIO] payload prvout payload with binary format
|
25
|
+
# @return [Bitcoin::OutPoint]
|
26
|
+
def self.parse_from_payload(payload)
|
27
|
+
buf = payload.is_a?(String) ? StringIO.new(payload) : payload
|
28
|
+
hash, index = buf.read(36).unpack('a32V')
|
29
|
+
OutPoint.new(hash.bth, index)
|
30
|
+
end
|
31
|
+
|
23
32
|
def coinbase?
|
24
33
|
tx_hash == COINBASE_HASH && index == COINBASE_INDEX
|
25
34
|
end
|
data/lib/bitcoin/psbt/input.rb
CHANGED
@@ -19,6 +19,12 @@ module Bitcoin
|
|
19
19
|
attr_accessor :hash160_preimages
|
20
20
|
attr_accessor :hash256_preimages
|
21
21
|
attr_accessor :proprietaries
|
22
|
+
attr_accessor :tap_key_sig
|
23
|
+
attr_accessor :tap_script_sigs
|
24
|
+
attr_accessor :tap_leaf_scripts
|
25
|
+
attr_accessor :tap_bip32_derivations
|
26
|
+
attr_accessor :tap_internal_key
|
27
|
+
attr_accessor :tap_merkle_root
|
22
28
|
attr_accessor :unknowns
|
23
29
|
|
24
30
|
def initialize(non_witness_utxo: nil, witness_utxo: nil)
|
@@ -31,6 +37,9 @@ module Bitcoin
|
|
31
37
|
@hash160_preimages = {}
|
32
38
|
@hash256_preimages = {}
|
33
39
|
@proprietaries = []
|
40
|
+
@tap_script_sigs = {}
|
41
|
+
@tap_leaf_scripts = {}
|
42
|
+
@tap_bip32_derivations = {}
|
34
43
|
@unknowns = {}
|
35
44
|
end
|
36
45
|
|
@@ -110,6 +119,35 @@ module Bitcoin
|
|
110
119
|
when PSBT_IN_TYPES[:proprietary]
|
111
120
|
raise ArgumentError, 'Duplicate Key, key for proprietary value already provided.' if input.proprietaries.any?{|p| p.key == key}
|
112
121
|
input.proprietaries << Proprietary.new(key, value)
|
122
|
+
when PSBT_IN_TYPES[:tap_key_sig]
|
123
|
+
raise ArgumentError, 'Size of key was not the expected size for the type tap key sig' unless key_len == 1
|
124
|
+
raise ArgumentError, 'Invalid schnorr signature size for the type tap key sig' unless [64, 65].include?(value.bytesize)
|
125
|
+
input.tap_key_sig = value.bth
|
126
|
+
when PSBT_IN_TYPES[:tap_script_sig]
|
127
|
+
raise ArgumentError, 'Duplicate Key, key for tap script sig value already provided.' if input.tap_script_sigs[key.bth]
|
128
|
+
raise ArgumentError, 'Size of key was not the expected size for the type tap script sig' unless key.bytesize == (X_ONLY_PUBKEY_SIZE + 32)
|
129
|
+
raise ArgumentError, 'Invalid schnorr signature size for the type tap script sig' unless [64, 65].include?(value.bytesize)
|
130
|
+
input.tap_script_sigs[key.bth] = value.bth
|
131
|
+
when PSBT_IN_TYPES[:tap_leaf_script]
|
132
|
+
begin
|
133
|
+
cb = Bitcoin::Taproot::ControlBlock.parse_from_payload(key)
|
134
|
+
raise ArgumentError, 'Duplicate Key, key for tap leaf script value already provided.' if input.tap_leaf_scripts[cb]
|
135
|
+
input.tap_leaf_scripts[cb] = value.bth
|
136
|
+
rescue Bitcoin::Taproot::Error => e
|
137
|
+
raise ArgumentError, e.message
|
138
|
+
end
|
139
|
+
when PSBT_IN_TYPES[:tap_bip32_derivation]
|
140
|
+
raise ArgumentError, 'Duplicate Key, key for tap bip32 derivation value already provided.' if input.tap_bip32_derivations[key.bth]
|
141
|
+
raise ArgumentError, 'Size of key was not the expected size for the type tap bip32 derivation' unless key.bytesize == X_ONLY_PUBKEY_SIZE
|
142
|
+
input.tap_bip32_derivations[key.bth] = value.bth
|
143
|
+
when PSBT_IN_TYPES[:tap_internal_key]
|
144
|
+
raise ArgumentError, 'Size of key was not the expected size for the type tap internal key' unless key_len == 1
|
145
|
+
raise ArgumentError, 'Invalid x-only public key size for the type tap internal key' unless value.bytesize == X_ONLY_PUBKEY_SIZE
|
146
|
+
input.tap_internal_key = value.bth
|
147
|
+
when PSBT_IN_TYPES[:tap_merkle_root]
|
148
|
+
raise ArgumentError, 'Size of key was not the expected size for the type tap merkle root' unless key_len == 1
|
149
|
+
raise ArgumentError, 'Invalid merkle root hash size for the type tap merkle root' unless value.bytesize == 32
|
150
|
+
input.tap_merkle_root = value.bth
|
113
151
|
else
|
114
152
|
unknown_key = ([key_type].pack('C') + key).bth
|
115
153
|
raise ArgumentError, 'Duplicate Key, key for unknown value already provided.' if input.unknowns[unknown_key]
|
@@ -135,6 +173,12 @@ module Bitcoin
|
|
135
173
|
payload << sha256_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:sha256], key: k.htb, value: v.htb)}.join
|
136
174
|
payload << hash160_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:hash160], key: k.htb, value: v.htb)}.join
|
137
175
|
payload << hash256_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:hash256], key: k.htb, value: v.htb)}.join
|
176
|
+
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_key_sig], value: tap_key_sig.htb) if tap_key_sig
|
177
|
+
payload << tap_script_sigs.map{|k, v| PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_script_sig], key: k.htb, value: v.htb)}.join
|
178
|
+
payload << tap_leaf_scripts.map{|k, v| PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_leaf_script], key: k.to_payload, value: v.htb)}.join
|
179
|
+
payload << tap_bip32_derivations.map{|k, v| PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_bip32_derivation], key: k.htb, value: v.htb)}.join
|
180
|
+
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_internal_key], value: tap_internal_key.htb) if tap_internal_key
|
181
|
+
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_merkle_root], value: tap_merkle_root.htb) if tap_merkle_root
|
138
182
|
end
|
139
183
|
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:script_sig], value: final_script_sig.to_payload) if final_script_sig
|
140
184
|
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:script_witness], value: final_script_witness.to_payload) if final_script_witness
|
data/lib/bitcoin/psbt/output.rb
CHANGED
@@ -8,11 +8,15 @@ module Bitcoin
|
|
8
8
|
attr_accessor :witness_script
|
9
9
|
attr_accessor :hd_key_paths
|
10
10
|
attr_accessor :proprietaries
|
11
|
+
attr_accessor :tap_internal_key
|
12
|
+
attr_accessor :tap_tree
|
13
|
+
attr_accessor :tap_bip32_derivations
|
11
14
|
attr_accessor :unknowns
|
12
15
|
|
13
16
|
def initialize
|
14
17
|
@hd_key_paths = {}
|
15
18
|
@proprietaries = []
|
19
|
+
@tap_bip32_derivations = {}
|
16
20
|
@unknowns = {}
|
17
21
|
end
|
18
22
|
|
@@ -46,6 +50,17 @@ module Bitcoin
|
|
46
50
|
when PSBT_OUT_TYPES[:proprietary]
|
47
51
|
raise ArgumentError, 'Duplicate Key, key for proprietary value already provided.' if output.proprietaries.any?{|p| p.key == key}
|
48
52
|
output.proprietaries << Proprietary.new(key, value)
|
53
|
+
when PSBT_OUT_TYPES[:tap_internal_key]
|
54
|
+
raise ArgumentError, 'Invalid output tap internal key typed key.' unless key_len == 1
|
55
|
+
raise ArgumentError, 'Invalid x-only public key size for the type tap internal key' unless value.bytesize == X_ONLY_PUBKEY_SIZE
|
56
|
+
output.tap_internal_key = value.bth
|
57
|
+
when PSBT_OUT_TYPES[:tap_tree]
|
58
|
+
raise ArgumentError, 'Invalid output tap tree typed key.' unless key_len == 1
|
59
|
+
output.tap_tree = value.bth # TODO implement builder.
|
60
|
+
when PSBT_OUT_TYPES[:tap_bip32_derivation]
|
61
|
+
raise ArgumentError, 'Duplicate Key, key for tap bip32 derivation value already provided.' if output.tap_bip32_derivations[key.bth]
|
62
|
+
raise ArgumentError, 'Size of key was not the expected size for the type tap bip32 derivation' unless key.bytesize == X_ONLY_PUBKEY_SIZE
|
63
|
+
output.tap_bip32_derivations[key.bth] = value.bth
|
49
64
|
else
|
50
65
|
unknown_key = ([key_type].pack('C') + key).bth
|
51
66
|
raise ArgumentError, 'Duplicate Key, key for unknown value already provided' if output.unknowns[unknown_key]
|
@@ -62,6 +77,9 @@ module Bitcoin
|
|
62
77
|
payload << PSBT.serialize_to_vector(PSBT_OUT_TYPES[:witness_script], value: witness_script) if witness_script
|
63
78
|
payload << hd_key_paths.values.map{|v|v.to_payload(PSBT_OUT_TYPES[:bip32_derivation])}.join
|
64
79
|
payload << proprietaries.map(&:to_payload).join
|
80
|
+
payload << PSBT.serialize_to_vector(PSBT_OUT_TYPES[:tap_internal_key], value: tap_internal_key.htb) if tap_internal_key
|
81
|
+
payload << PSBT.serialize_to_vector(PSBT_OUT_TYPES[:tap_tree], value: tap_tree.htb) if tap_tree
|
82
|
+
payload << tap_bip32_derivations.map{|k, v|PSBT.serialize_to_vector(PSBT_OUT_TYPES[:tap_bip32_derivation], key: k.htb, value: v.htb)}.join
|
65
83
|
payload << unknowns.map {|k,v|Bitcoin.pack_var_int(k.htb.bytesize) << k.htb << Bitcoin.pack_var_int(v.bytesize) << v}.join
|
66
84
|
payload << PSBT_SEPARATOR.itb
|
67
85
|
payload
|
data/lib/bitcoin/psbt.rb
CHANGED
@@ -33,12 +33,21 @@ module Bitcoin
|
|
33
33
|
sha256: 0x0b,
|
34
34
|
hash160: 0x0c,
|
35
35
|
hash256: 0x0d,
|
36
|
+
tap_key_sig: 0x13,
|
37
|
+
tap_script_sig: 0x14,
|
38
|
+
tap_leaf_script: 0x15,
|
39
|
+
tap_bip32_derivation: 0x16,
|
40
|
+
tap_internal_key: 0x17,
|
41
|
+
tap_merkle_root: 0x18,
|
36
42
|
proprietary: 0xfc
|
37
43
|
}
|
38
44
|
PSBT_OUT_TYPES = {
|
39
45
|
redeem_script: 0x00,
|
40
46
|
witness_script: 0x01,
|
41
47
|
bip32_derivation: 0x02,
|
48
|
+
tap_internal_key: 0x05,
|
49
|
+
tap_tree: 0x06,
|
50
|
+
tap_bip32_derivation: 0x07,
|
42
51
|
proprietary: 0xfc
|
43
52
|
}
|
44
53
|
PSBT_SEPARATOR = 0x00
|
@@ -15,15 +15,28 @@ module Bitcoin
|
|
15
15
|
end
|
16
16
|
|
17
17
|
# generate P2PKH script
|
18
|
+
# @param [String] pubkey_hash public key hash with hex format
|
18
19
|
def self.to_p2pkh(pubkey_hash)
|
19
20
|
new << OP_DUP << OP_HASH160 << pubkey_hash << OP_EQUALVERIFY << OP_CHECKSIG
|
20
21
|
end
|
21
22
|
|
22
23
|
# generate P2WPKH script
|
24
|
+
# @param [String] pubkey_hash public key hash with hex format
|
25
|
+
# @return [Bitcoin::Script]
|
23
26
|
def self.to_p2wpkh(pubkey_hash)
|
24
27
|
new << WITNESS_VERSION_V0 << pubkey_hash
|
25
28
|
end
|
26
29
|
|
30
|
+
# Generate P2TR script
|
31
|
+
# @param [String or Bitcoin::Key] xonly_pubkey public key with hex format or Bitcoin::Key
|
32
|
+
# @return [Bitcoin::Script]
|
33
|
+
# @raise ArgumentError
|
34
|
+
def self.to_p2tr(xonly_pubkey)
|
35
|
+
xonly_pubkey = xonly_pubkey.xonly_pubkey if xonly_pubkey.is_a?(Bitcoin::Key)
|
36
|
+
raise ArgumentError, 'Invalid public key size' unless xonly_pubkey.htb.bytesize == Bitcoin::WITNESS_V1_TAPROOT_SIZE
|
37
|
+
new << OP_1 << xonly_pubkey
|
38
|
+
end
|
39
|
+
|
27
40
|
# generate m of n multisig p2sh script
|
28
41
|
# @param [String] m the number of signatures required for multisig
|
29
42
|
# @param [Array] pubkeys array of public keys that compose multisig
|
@@ -136,16 +136,15 @@ module Bitcoin
|
|
136
136
|
else
|
137
137
|
sig_version = :tapscript
|
138
138
|
# Script path spending (stack size is >1 after removing optional annex)
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
(control.bytesize - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE != 0
|
139
|
+
begin
|
140
|
+
control = Bitcoin::Taproot::ControlBlock.parse_from_payload(stack.pop.htb)
|
141
|
+
rescue Bitcoin::Taproot::Error
|
143
142
|
return set_error(SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE)
|
144
143
|
end
|
145
|
-
|
146
|
-
opts[:leaf_hash] = Bitcoin.tagged_hash('TapLeaf', [leaf_ver].pack('C') + Bitcoin.pack_var_string(script_payload))
|
144
|
+
script_payload = stack.pop.htb
|
145
|
+
opts[:leaf_hash] = Bitcoin.tagged_hash('TapLeaf', [control.leaf_ver].pack('C') + Bitcoin.pack_var_string(script_payload))
|
147
146
|
return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless valid_taproot_commitment?(control, program, opts[:leaf_hash])
|
148
|
-
if
|
147
|
+
if control.leaf_ver == TAPROOT_LEAF_TAPSCRIPT
|
149
148
|
opts[:weight_left] = witness.to_payload.bytesize + VALIDATION_WEIGHT_OFFSET
|
150
149
|
script_pubkey = Bitcoin::Script.parse_from_payload(script_payload)
|
151
150
|
script_pubkey.chunks.each do |c|
|
@@ -698,19 +697,11 @@ module Bitcoin
|
|
698
697
|
# check whether valid taproot commitment.
|
699
698
|
def valid_taproot_commitment?(control, program, leaf_hash)
|
700
699
|
begin
|
701
|
-
|
702
|
-
xonly_pubkey = control[1...TAPROOT_CONTROL_BASE_SIZE]
|
703
|
-
p = Bitcoin::Key.from_xonly_pubkey(xonly_pubkey.bth)
|
700
|
+
p = Bitcoin::Key.from_xonly_pubkey(control.internal_key)
|
704
701
|
k = leaf_hash
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
k = Bitcoin.tagged_hash('TapBranch', k.bth < e.bth ? k + e : e + k)
|
709
|
-
end
|
710
|
-
t = Bitcoin.tagged_hash('TapTweak', xonly_pubkey + k)
|
711
|
-
key = Bitcoin::Key.new(priv_key: t.bth, key_type: Key::TYPES[:compressed])
|
712
|
-
q = key.to_point + p.to_point
|
713
|
-
return q.x == program.bti && (control[0].bti & 1) == (q.y % 2)
|
702
|
+
control.paths.each { |e| k = Bitcoin.tagged_hash('TapBranch', k.bth < e ? k + e.htb : e.htb + k) }
|
703
|
+
q = Bitcoin::Taproot.tweak_public_key(p, k.bth).to_point
|
704
|
+
return q.x == program.bti && control.parity == (q.y % 2)
|
714
705
|
rescue ArgumentError
|
715
706
|
return false
|
716
707
|
end
|
@@ -751,7 +742,7 @@ module Bitcoin
|
|
751
742
|
pubkey_size = pubkey.htb.bytesize
|
752
743
|
if pubkey_size == 0
|
753
744
|
return set_error(SCRIPT_ERR_PUBKEYTYPE)
|
754
|
-
elsif pubkey_size ==
|
745
|
+
elsif pubkey_size == X_ONLY_PUBKEY_SIZE
|
755
746
|
if success
|
756
747
|
result = checker.check_schnorr_sig(sig, pubkey, :tapscript, opts)
|
757
748
|
return checker.has_error? ? set_error(checker.error_code) : set_error(SCRIPT_ERR_SCHNORR_SIG) unless result
|
@@ -760,7 +751,6 @@ module Bitcoin
|
|
760
751
|
return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE) if flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE)
|
761
752
|
end
|
762
753
|
success
|
763
|
-
# return true
|
764
754
|
end
|
765
755
|
|
766
756
|
end
|
@@ -232,7 +232,7 @@ module Bitcoin
|
|
232
232
|
def full_pubkey_from_xonly_pubkey(pub_key)
|
233
233
|
with_context do |context|
|
234
234
|
pubkey = pub_key.htb
|
235
|
-
raise ArgumentError,
|
235
|
+
raise ArgumentError, "Pubkey size must be #{X_ONLY_PUBKEY_SIZE} bytes." unless pubkey.bytesize == X_ONLY_PUBKEY_SIZE
|
236
236
|
xonly_pubkey = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
|
237
237
|
full_pubkey = FFI::MemoryPointer.new(:uchar, 64)
|
238
238
|
raise ArgumentError, 'An invalid public key was specified.' unless secp256k1_xonly_pubkey_parse(context, full_pubkey, xonly_pubkey) == 1
|
@@ -32,7 +32,7 @@ module Bitcoin
|
|
32
32
|
# @return [Boolean] result.
|
33
33
|
def valid_xonly_pubkey?(pub_key)
|
34
34
|
pubkey = pub_key.htb
|
35
|
-
return false unless pubkey.bytesize ==
|
35
|
+
return false unless pubkey.bytesize == X_ONLY_PUBKEY_SIZE
|
36
36
|
begin
|
37
37
|
ECDSA::Format::PointOctetString.decode(pubkey, ECDSA::Group::Secp256k1)
|
38
38
|
rescue Exception
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Taproot
|
3
|
+
class ControlBlock
|
4
|
+
include Bitcoin::HexConverter
|
5
|
+
|
6
|
+
attr_accessor :parity
|
7
|
+
attr_accessor :leaf_ver
|
8
|
+
attr_accessor :internal_key
|
9
|
+
attr_accessor :paths
|
10
|
+
|
11
|
+
# @param [Integer] parity
|
12
|
+
# @param [Integer] leaf_ver
|
13
|
+
# @param [String] internal_key public key with hex format.
|
14
|
+
# @param [Array[String]] paths array of hash values of sibling nodes in the tree that serve as proof of inclusion
|
15
|
+
def initialize(parity, leaf_ver, internal_key, paths = [])
|
16
|
+
@parity = parity
|
17
|
+
@leaf_ver = leaf_ver
|
18
|
+
@internal_key = internal_key
|
19
|
+
@paths = paths
|
20
|
+
end
|
21
|
+
|
22
|
+
# parse control block from payload.
|
23
|
+
# @raise [Bitcoin::Taproot::Error]
|
24
|
+
# @return [Bitcoin::Taproot::ControlBlock]
|
25
|
+
def self.parse_from_payload(payload)
|
26
|
+
raise Bitcoin::Taproot::Error, 'Invalid data length for Control Block' if payload.bytesize > TAPROOT_CONTROL_MAX_SIZE
|
27
|
+
raise Bitcoin::Taproot::Error, 'Invalid data length for path in Control Block' unless (payload.bytesize - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE == 0
|
28
|
+
control, internal_key, paths = payload.unpack('Ca32a*')
|
29
|
+
parity = control & 1
|
30
|
+
leaf_ver = control - parity
|
31
|
+
raw_paths = StringIO.new(paths)
|
32
|
+
paths = (paths.bytesize / 32).times.map { raw_paths.read(32).bth }
|
33
|
+
ControlBlock.new(parity, leaf_ver, internal_key.bth, paths)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Convert to payload.
|
37
|
+
# @return [String] payload with binary format.
|
38
|
+
def to_payload
|
39
|
+
[parity + leaf_ver].pack("C") + internal_key.htb + paths.map(&:htb).join
|
40
|
+
end
|
41
|
+
|
42
|
+
def ==(other)
|
43
|
+
to_payload == other.to_payload
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -16,7 +16,7 @@ module Bitcoin
|
|
16
16
|
# @raise [Bitcoin::Taproot::Builder] +internal_pubkey+ dose not xonly public key or leaf in +leaves+ does not instance of Bitcoin::Taproot::LeafNode.
|
17
17
|
# @return [Bitcoin::Taproot::SimpleBuilder]
|
18
18
|
def initialize(internal_key, leaves = [])
|
19
|
-
raise Error,
|
19
|
+
raise Error, "Internal public key must be #{X_ONLY_PUBKEY_SIZE} bytes" unless internal_key.htb.bytesize == X_ONLY_PUBKEY_SIZE
|
20
20
|
raise Error, 'leaf must be Bitcoin::Taproot::LeafNode object' if leaves.find{ |leaf| !leaf.is_a?(Bitcoin::Taproot::LeafNode)}
|
21
21
|
|
22
22
|
@leaves = leaves
|
@@ -72,11 +72,11 @@ module Bitcoin
|
|
72
72
|
|
73
73
|
# Generate control block needed to unlock with script-path.
|
74
74
|
# @param [Bitcoin::Taproot::LeafNode] leaf Leaf to use for unlocking.
|
75
|
-
# @return [
|
75
|
+
# @return [Bitcoin::Taproot::ControlBlock] control block.
|
76
76
|
def control_block(leaf)
|
77
77
|
path = inclusion_proof(leaf)
|
78
78
|
parity = tweak_public_key.to_point.has_even_y? ? 0 : 1
|
79
|
-
|
79
|
+
ControlBlock.new(parity, leaf.leaf_ver, internal_key, path.map(&:bth))
|
80
80
|
end
|
81
81
|
|
82
82
|
# Generate inclusion proof for +leaf+.
|
data/lib/bitcoin/taproot.rb
CHANGED
data/lib/bitcoin/tx_in.rb
CHANGED
@@ -34,8 +34,7 @@ module Bitcoin
|
|
34
34
|
def self.parse_from_payload(payload)
|
35
35
|
buf = payload.is_a?(String) ? StringIO.new(payload) : payload
|
36
36
|
i = new
|
37
|
-
|
38
|
-
i.out_point = OutPoint.new(hash.bth, index)
|
37
|
+
i.out_point = OutPoint.parse_from_payload(buf)
|
39
38
|
sig_length = Bitcoin.unpack_var_int_from_io(buf)
|
40
39
|
if sig_length == 0
|
41
40
|
i.script_sig = Bitcoin::Script.new
|
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: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- azuchi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-10-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ecdsa
|
@@ -479,6 +479,7 @@ files:
|
|
479
479
|
- lib/bitcoin/store/spv_chain.rb
|
480
480
|
- lib/bitcoin/store/utxo_db.rb
|
481
481
|
- lib/bitcoin/taproot.rb
|
482
|
+
- lib/bitcoin/taproot/control_block.rb
|
482
483
|
- lib/bitcoin/taproot/leaf_node.rb
|
483
484
|
- lib/bitcoin/taproot/simple_builder.rb
|
484
485
|
- lib/bitcoin/tx.rb
|
@@ -516,7 +517,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
516
517
|
- !ruby/object:Gem::Version
|
517
518
|
version: '0'
|
518
519
|
requirements: []
|
519
|
-
rubygems_version: 3.3.
|
520
|
+
rubygems_version: 3.3.23
|
520
521
|
signing_key:
|
521
522
|
specification_version: 4
|
522
523
|
summary: The implementation of Bitcoin Protocol for Ruby.
|