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