bitcoinrb 1.12.1 → 1.14.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/bitcoinrb.gemspec +2 -2
- data/lib/bitcoin/base58.rb +15 -3
- data/lib/bitcoin/bip321_uri.rb +3 -6
- data/lib/bitcoin/bip324/fs_chacha20.rb +1 -1
- data/lib/bitcoin/bip324/fs_chacha_poly1305.rb +1 -0
- data/lib/bitcoin/bip324.rb +6 -1
- data/lib/bitcoin/block.rb +3 -3
- data/lib/bitcoin/descriptor/checksum.rb +6 -2
- data/lib/bitcoin/ext_key.rb +8 -4
- data/lib/bitcoin/key.rb +2 -2
- data/lib/bitcoin/message/base.rb +7 -1
- data/lib/bitcoin/message/cf_parser.rb +2 -2
- data/lib/bitcoin/message/fee_filter.rb +2 -2
- data/lib/bitcoin/message/filter_load.rb +10 -0
- data/lib/bitcoin/message/header_and_short_ids.rb +4 -4
- data/lib/bitcoin/message/inventory.rb +1 -1
- data/lib/bitcoin/message/merkle_block.rb +6 -0
- data/lib/bitcoin/message/network_addr.rb +7 -3
- data/lib/bitcoin/message/ping.rb +2 -2
- data/lib/bitcoin/message/pong.rb +2 -2
- data/lib/bitcoin/message/send_cmpct.rb +2 -2
- data/lib/bitcoin/message/version.rb +3 -3
- data/lib/bitcoin/message.rb +15 -2
- data/lib/bitcoin/mnemonic.rb +9 -4
- data/lib/bitcoin/partial_tree.rb +25 -0
- data/lib/bitcoin/psbt/input.rb +58 -33
- data/lib/bitcoin/psbt/key_origin_info.rb +2 -2
- data/lib/bitcoin/psbt/output.rb +9 -5
- data/lib/bitcoin/psbt/tx.rb +22 -15
- data/lib/bitcoin/psbt.rb +1 -1
- data/lib/bitcoin/rpc/bitcoin_core_client.rb +3 -2
- data/lib/bitcoin/script/script.rb +15 -5
- data/lib/bitcoin/script/script_interpreter.rb +1 -1
- data/lib/bitcoin/script/tx_checker.rb +7 -2
- data/lib/bitcoin/secp256k1/native.rb +50 -1
- data/lib/bitcoin/secp256k1/rfc6979.rb +6 -3
- data/lib/bitcoin/secp256k1/ruby.rb +115 -2
- data/lib/bitcoin/sighash_generator.rb +8 -2
- data/lib/bitcoin/silent_payment.rb +56 -90
- data/lib/bitcoin/taproot/control_block.rb +1 -0
- data/lib/bitcoin/taproot/custom_depth_builder.rb +2 -2
- data/lib/bitcoin/taproot/simple_builder.rb +9 -7
- data/lib/bitcoin/taproot.rb +6 -1
- data/lib/bitcoin/tx.rb +8 -6
- data/lib/bitcoin/tx_out.rb +2 -2
- data/lib/bitcoin/util.rb +4 -4
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet/account.rb +2 -2
- data/lib/bitcoin/wallet/db.rb +3 -3
- data/lib/bitcoin/wallet/master_key.rb +83 -24
- data/lib/bitcoin/wallet/utxo.rb +2 -2
- data/lib/bitcoin.rb +1 -0
- metadata +5 -5
data/lib/bitcoin/psbt/input.rb
CHANGED
|
@@ -55,8 +55,9 @@ module Bitcoin
|
|
|
55
55
|
found_sep = true
|
|
56
56
|
break
|
|
57
57
|
end
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
key_start = buf.pos
|
|
59
|
+
key_type = Bitcoin.unpack_var_int_from_io(buf)
|
|
60
|
+
key = buf.read(key_len - (buf.pos - key_start))
|
|
60
61
|
value = buf.read(Bitcoin.unpack_var_int_from_io(buf))
|
|
61
62
|
|
|
62
63
|
case key_type
|
|
@@ -79,7 +80,8 @@ module Bitcoin
|
|
|
79
80
|
when PSBT_IN_TYPES[:sighash]
|
|
80
81
|
raise ArgumentError, 'Invalid input sighash type typed key.' unless key_len == 1
|
|
81
82
|
raise ArgumentError, 'Duplicate Key, input sighash type already provided.' if input.sighash_type
|
|
82
|
-
input.
|
|
83
|
+
raise ArgumentError, 'Invalid input sighash type value.' unless value.bytesize == 4
|
|
84
|
+
input.sighash_type = value.unpack1('V')
|
|
83
85
|
when PSBT_IN_TYPES[:redeem_script]
|
|
84
86
|
raise ArgumentError, 'Invalid redeemscript typed key.' unless key_len == 1
|
|
85
87
|
raise ArgumentError, 'Duplicate Key, input redeemScript already provided.' if input.redeem_script
|
|
@@ -117,10 +119,12 @@ module Bitcoin
|
|
|
117
119
|
raise ArgumentError, 'Duplicate Key, input hash256 preimage already provided' if input.hash256_preimages[key.bth]
|
|
118
120
|
input.hash256_preimages[key.bth] = value.bth
|
|
119
121
|
when PSBT_IN_TYPES[:proprietary]
|
|
120
|
-
|
|
121
|
-
input.proprietaries
|
|
122
|
+
proprietary = Proprietary.new(key, value)
|
|
123
|
+
raise ArgumentError, 'Duplicate Key, key for proprietary value already provided.' if input.proprietaries.any?{|p| p.key == proprietary.key}
|
|
124
|
+
input.proprietaries << proprietary
|
|
122
125
|
when PSBT_IN_TYPES[:tap_key_sig]
|
|
123
126
|
raise ArgumentError, 'Size of key was not the expected size for the type tap key sig' unless key_len == 1
|
|
127
|
+
raise ArgumentError, 'Duplicate Key, input tap key sig already provided.' if input.tap_key_sig
|
|
124
128
|
raise ArgumentError, 'Invalid schnorr signature size for the type tap key sig' unless [64, 65].include?(value.bytesize)
|
|
125
129
|
input.tap_key_sig = value.bth
|
|
126
130
|
when PSBT_IN_TYPES[:tap_script_sig]
|
|
@@ -142,14 +146,16 @@ module Bitcoin
|
|
|
142
146
|
input.tap_bip32_derivations[key.bth] = value.bth
|
|
143
147
|
when PSBT_IN_TYPES[:tap_internal_key]
|
|
144
148
|
raise ArgumentError, 'Size of key was not the expected size for the type tap internal key' unless key_len == 1
|
|
149
|
+
raise ArgumentError, 'Duplicate Key, input tap internal key already provided.' if input.tap_internal_key
|
|
145
150
|
raise ArgumentError, 'Invalid x-only public key size for the type tap internal key' unless value.bytesize == X_ONLY_PUBKEY_SIZE
|
|
146
151
|
input.tap_internal_key = value.bth
|
|
147
152
|
when PSBT_IN_TYPES[:tap_merkle_root]
|
|
148
153
|
raise ArgumentError, 'Size of key was not the expected size for the type tap merkle root' unless key_len == 1
|
|
154
|
+
raise ArgumentError, 'Duplicate Key, input tap merkle root already provided.' if input.tap_merkle_root
|
|
149
155
|
raise ArgumentError, 'Invalid merkle root hash size for the type tap merkle root' unless value.bytesize == 32
|
|
150
156
|
input.tap_merkle_root = value.bth
|
|
151
157
|
else
|
|
152
|
-
unknown_key = (
|
|
158
|
+
unknown_key = (Bitcoin.pack_var_int(key_type) + key).bth
|
|
153
159
|
raise ArgumentError, 'Duplicate Key, key for unknown value already provided.' if input.unknowns[unknown_key]
|
|
154
160
|
input.unknowns[unknown_key] = value
|
|
155
161
|
end
|
|
@@ -163,23 +169,21 @@ module Bitcoin
|
|
|
163
169
|
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:non_witness_utxo], value:
|
|
164
170
|
(witness_utxo && valid_witness_input?) ? non_witness_utxo.serialize_old_format : non_witness_utxo.to_payload) if non_witness_utxo
|
|
165
171
|
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:witness_utxo], value: witness_utxo.to_payload) if witness_utxo
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_merkle_root], value: tap_merkle_root.htb) if tap_merkle_root
|
|
182
|
-
end
|
|
172
|
+
payload << partial_sigs.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:partial_sig], key: k.htb, value: v)}.join
|
|
173
|
+
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:sighash], value: [sighash_type].pack('V')) if sighash_type
|
|
174
|
+
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:redeem_script], value: redeem_script.to_payload) if redeem_script
|
|
175
|
+
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:witness_script], value: witness_script.to_payload) if witness_script
|
|
176
|
+
payload << hd_key_paths.values.map(&:to_payload).join
|
|
177
|
+
payload << ripemd160_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:ripemd160], key: k.htb, value: v.htb)}.join
|
|
178
|
+
payload << sha256_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:sha256], key: k.htb, value: v.htb)}.join
|
|
179
|
+
payload << hash160_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:hash160], key: k.htb, value: v.htb)}.join
|
|
180
|
+
payload << hash256_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:hash256], key: k.htb, value: v.htb)}.join
|
|
181
|
+
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_key_sig], value: tap_key_sig.htb) if tap_key_sig
|
|
182
|
+
payload << tap_script_sigs.map{|k, v| PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_script_sig], key: k.htb, value: v.htb)}.join
|
|
183
|
+
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
|
|
184
|
+
payload << tap_bip32_derivations.map{|k, v| PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_bip32_derivation], key: k.htb, value: v.htb)}.join
|
|
185
|
+
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_internal_key], value: tap_internal_key.htb) if tap_internal_key
|
|
186
|
+
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_merkle_root], value: tap_merkle_root.htb) if tap_merkle_root
|
|
183
187
|
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:script_sig], value: final_script_sig.to_payload) if final_script_sig
|
|
184
188
|
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:script_witness], value: final_script_witness.to_payload) if final_script_witness
|
|
185
189
|
payload << proprietaries.map(&:to_payload).join
|
|
@@ -188,13 +192,23 @@ module Bitcoin
|
|
|
188
192
|
payload
|
|
189
193
|
end
|
|
190
194
|
|
|
195
|
+
# Get the previous output this input spends.
|
|
196
|
+
# @param [Integer] index The index of the outpoint this input refers to.
|
|
197
|
+
# @return [Bitcoin::TxOut] The previous output, or nil if this input does not carry it.
|
|
198
|
+
def utxo(index)
|
|
199
|
+
(non_witness_utxo ? non_witness_utxo.out[index] : nil) || witness_utxo
|
|
200
|
+
end
|
|
201
|
+
|
|
191
202
|
# Check whether input's scriptPubkey is correct witness.
|
|
192
203
|
# @return [Boolean]
|
|
193
204
|
def valid_witness_input?
|
|
194
|
-
return
|
|
195
|
-
return true if witness_utxo
|
|
205
|
+
return false unless witness_utxo
|
|
206
|
+
return true if witness_utxo.script_pubkey.p2wpkh? # P2WPKH
|
|
207
|
+
# P2WSH. The scriptPubkey commits to the witness script directly, so a redeem script is not used.
|
|
208
|
+
return true if witness_utxo.script_pubkey.p2wsh? && witness_script &&
|
|
209
|
+
Bitcoin::Script.to_p2wsh(witness_script) == witness_utxo.script_pubkey
|
|
196
210
|
# segwit nested in P2SH
|
|
197
|
-
if witness_utxo
|
|
211
|
+
if witness_utxo.script_pubkey.p2sh? && redeem_script&.witness_program? && redeem_script.to_p2sh == witness_utxo.script_pubkey
|
|
198
212
|
return true if redeem_script.p2wpkh?# nested p2wpkh
|
|
199
213
|
return true if witness_script&.to_sha256 == redeem_script.witness_data[1].bth # nested p2wsh
|
|
200
214
|
end
|
|
@@ -235,19 +249,19 @@ module Bitcoin
|
|
|
235
249
|
# @return [Bitcoin::PSBT::Input] combined object.
|
|
236
250
|
def merge(psbi)
|
|
237
251
|
raise ArgumentError, 'The argument psbt must be an instance of Bitcoin::PSBT::Input.' unless psbi.is_a?(Bitcoin::PSBT::Input)
|
|
238
|
-
raise ArgumentError, 'The Partially Signed Input\'s non_witness_utxo are different.' unless non_witness_utxo
|
|
239
|
-
raise ArgumentError, 'The Partially Signed Input\'s witness_utxo are different.' unless witness_utxo
|
|
252
|
+
raise ArgumentError, 'The Partially Signed Input\'s non_witness_utxo are different.' unless same_field?(non_witness_utxo, psbi.non_witness_utxo)
|
|
253
|
+
raise ArgumentError, 'The Partially Signed Input\'s witness_utxo are different.' unless same_field?(witness_utxo, psbi.witness_utxo)
|
|
240
254
|
raise ArgumentError, 'The Partially Signed Input\'s sighash_type are different.' if sighash_type && psbi.sighash_type && sighash_type != psbi.sighash_type
|
|
241
|
-
raise ArgumentError, 'The Partially Signed Input\'s redeem_script are different.' unless redeem_script
|
|
242
|
-
raise ArgumentError, 'The Partially Signed Input\'s witness_script are different.' unless witness_script
|
|
255
|
+
raise ArgumentError, 'The Partially Signed Input\'s redeem_script are different.' unless same_field?(redeem_script, psbi.redeem_script)
|
|
256
|
+
raise ArgumentError, 'The Partially Signed Input\'s witness_script are different.' unless same_field?(witness_script, psbi.witness_script)
|
|
243
257
|
combined = Bitcoin::PSBT::Input.new(non_witness_utxo: non_witness_utxo, witness_utxo: witness_utxo)
|
|
244
258
|
combined.unknowns = Hash[unknowns.merge(psbi.unknowns).sort]
|
|
245
259
|
combined.redeem_script = redeem_script
|
|
246
260
|
combined.witness_script = witness_script
|
|
247
261
|
combined.sighash_type = sighash_type
|
|
248
262
|
sigs = Hash[partial_sigs.merge(psbi.partial_sigs)]
|
|
249
|
-
redeem_script.get_multisig_pubkeys.each{|pubkey|combined.partial_sigs[pubkey.bth] = sigs[pubkey.bth]} if redeem_script&.multisig?
|
|
250
|
-
witness_script.get_multisig_pubkeys.each{|pubkey|combined.partial_sigs[pubkey.bth] = sigs[pubkey.bth]} if witness_script&.multisig?
|
|
263
|
+
redeem_script.get_multisig_pubkeys.each{|pubkey|combined.partial_sigs[pubkey.bth] = sigs[pubkey.bth] if sigs[pubkey.bth]} if redeem_script&.multisig?
|
|
264
|
+
witness_script.get_multisig_pubkeys.each{|pubkey|combined.partial_sigs[pubkey.bth] = sigs[pubkey.bth] if sigs[pubkey.bth]} if witness_script&.multisig?
|
|
251
265
|
combined.hd_key_paths = hd_key_paths.merge(psbi.hd_key_paths)
|
|
252
266
|
combined
|
|
253
267
|
end
|
|
@@ -288,7 +302,7 @@ module Bitcoin
|
|
|
288
302
|
h[:non_witness_utxo] = non_witness_utxo.to_h if non_witness_utxo
|
|
289
303
|
h[:witness_utxo] = witness_utxo.to_h if witness_utxo
|
|
290
304
|
h[:redeem_script] = redeem_script.to_h if redeem_script
|
|
291
|
-
h[:witness_script] = witness_script.to_h if
|
|
305
|
+
h[:witness_script] = witness_script.to_h if witness_script
|
|
292
306
|
h[:final_script_sig] = final_script_sig.to_h if final_script_sig
|
|
293
307
|
h[:final_script_witness] = final_script_witness.to_h if final_script_witness
|
|
294
308
|
h[:bip32_derivs] = hd_key_paths.values.map(&:to_h) unless hd_key_paths.empty?
|
|
@@ -309,6 +323,17 @@ module Bitcoin
|
|
|
309
323
|
h
|
|
310
324
|
end
|
|
311
325
|
|
|
326
|
+
private
|
|
327
|
+
|
|
328
|
+
# Whether +a+ and +b+ are the same field value. Unlike ==, this does not raise
|
|
329
|
+
# NoMethodError when only one of them is nil.
|
|
330
|
+
# @return [Boolean]
|
|
331
|
+
def same_field?(a, b)
|
|
332
|
+
return true if a.nil? && b.nil?
|
|
333
|
+
return false if a.nil? || b.nil?
|
|
334
|
+
a == b
|
|
335
|
+
end
|
|
336
|
+
|
|
312
337
|
end
|
|
313
338
|
|
|
314
339
|
end
|
|
@@ -15,11 +15,11 @@ module Bitcoin
|
|
|
15
15
|
|
|
16
16
|
def self.parse_from_payload(payload)
|
|
17
17
|
buf = StringIO.new(payload)
|
|
18
|
-
self.new(fingerprint: buf.read(4).bth, key_paths: buf.read.unpack('
|
|
18
|
+
self.new(fingerprint: buf.read(4).bth, key_paths: buf.read.unpack('V*'))
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def to_payload
|
|
22
|
-
fingerprint.htb + key_paths.pack('
|
|
22
|
+
fingerprint.htb + key_paths.pack('V*')
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def to_h
|
data/lib/bitcoin/psbt/output.rb
CHANGED
|
@@ -32,8 +32,9 @@ module Bitcoin
|
|
|
32
32
|
found_sep = true
|
|
33
33
|
break
|
|
34
34
|
end
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
key_start = buf.pos
|
|
36
|
+
key_type = Bitcoin.unpack_var_int_from_io(buf)
|
|
37
|
+
key = buf.read(key_len - (buf.pos - key_start))
|
|
37
38
|
value = buf.read(Bitcoin.unpack_var_int_from_io(buf))
|
|
38
39
|
case key_type
|
|
39
40
|
when PSBT_OUT_TYPES[:redeem_script]
|
|
@@ -48,21 +49,24 @@ module Bitcoin
|
|
|
48
49
|
raise ArgumentError, 'Duplicate Key, pubkey derivation path already provided' if output.hd_key_paths[key.bth]
|
|
49
50
|
output.hd_key_paths[key.bth] = Bitcoin::PSBT::HDKeyPath.new(key, Bitcoin::PSBT::KeyOriginInfo.parse_from_payload(value))
|
|
50
51
|
when PSBT_OUT_TYPES[:proprietary]
|
|
51
|
-
|
|
52
|
-
output.proprietaries
|
|
52
|
+
proprietary = Proprietary.new(key, value)
|
|
53
|
+
raise ArgumentError, 'Duplicate Key, key for proprietary value already provided.' if output.proprietaries.any?{|p| p.key == proprietary.key}
|
|
54
|
+
output.proprietaries << proprietary
|
|
53
55
|
when PSBT_OUT_TYPES[:tap_internal_key]
|
|
54
56
|
raise ArgumentError, 'Invalid output tap internal key typed key.' unless key_len == 1
|
|
57
|
+
raise ArgumentError, 'Duplicate Key, output tap internal key already provided.' if output.tap_internal_key
|
|
55
58
|
raise ArgumentError, 'Invalid x-only public key size for the type tap internal key' unless value.bytesize == X_ONLY_PUBKEY_SIZE
|
|
56
59
|
output.tap_internal_key = value.bth
|
|
57
60
|
when PSBT_OUT_TYPES[:tap_tree]
|
|
58
61
|
raise ArgumentError, 'Invalid output tap tree typed key.' unless key_len == 1
|
|
62
|
+
raise ArgumentError, 'Duplicate Key, output tap tree already provided.' if output.tap_tree
|
|
59
63
|
output.tap_tree = value.bth # TODO implement builder.
|
|
60
64
|
when PSBT_OUT_TYPES[:tap_bip32_derivation]
|
|
61
65
|
raise ArgumentError, 'Duplicate Key, key for tap bip32 derivation value already provided.' if output.tap_bip32_derivations[key.bth]
|
|
62
66
|
raise ArgumentError, 'Size of key was not the expected size for the type tap bip32 derivation' unless key.bytesize == X_ONLY_PUBKEY_SIZE
|
|
63
67
|
output.tap_bip32_derivations[key.bth] = value.bth
|
|
64
68
|
else
|
|
65
|
-
unknown_key = (
|
|
69
|
+
unknown_key = (Bitcoin.pack_var_int(key_type) + key).bth
|
|
66
70
|
raise ArgumentError, 'Duplicate Key, key for unknown value already provided' if output.unknowns[unknown_key]
|
|
67
71
|
output.unknowns[unknown_key] = value
|
|
68
72
|
end
|
data/lib/bitcoin/psbt/tx.rb
CHANGED
|
@@ -68,8 +68,9 @@ module Bitcoin
|
|
|
68
68
|
found_sep = true
|
|
69
69
|
break
|
|
70
70
|
end
|
|
71
|
+
key_start = buf.pos
|
|
71
72
|
key_type = Bitcoin.unpack_var_int_from_io(buf)
|
|
72
|
-
key = buf.read(key_len -
|
|
73
|
+
key = buf.read(key_len - (buf.pos - key_start))
|
|
73
74
|
value = buf.read(Bitcoin.unpack_var_int_from_io(buf))
|
|
74
75
|
|
|
75
76
|
case key_type
|
|
@@ -89,14 +90,19 @@ module Bitcoin
|
|
|
89
90
|
raise ArgumentError, "global xpub's depth and the number of indexes not matched." unless xpub.depth == info.key_paths.size
|
|
90
91
|
partial_tx.xpubs << Bitcoin::PSBT::GlobalXpub.new(xpub, info)
|
|
91
92
|
when PSBT_GLOBAL_TYPES[:ver]
|
|
93
|
+
raise ArgumentError, 'Invalid global version typed key.' unless key_len == 1
|
|
94
|
+
raise ArgumentError, 'Duplicate Key, global version already provided.' if partial_tx.version_number
|
|
95
|
+
raise ArgumentError, 'Invalid global version value.' unless value.bytesize == 4
|
|
92
96
|
partial_tx.version_number = value.unpack1('V')
|
|
93
97
|
raise ArgumentError, "An unsupported version was detected." if SUPPORT_VERSION < partial_tx.version_number
|
|
94
98
|
when PSBT_GLOBAL_TYPES[:proprietary]
|
|
95
|
-
|
|
96
|
-
partial_tx.proprietaries
|
|
99
|
+
proprietary = Proprietary.new(key, value)
|
|
100
|
+
raise ArgumentError, 'Duplicate Key, key for proprietary value already provided.' if partial_tx.proprietaries.any?{|p| p.key == proprietary.key}
|
|
101
|
+
partial_tx.proprietaries << proprietary
|
|
97
102
|
else
|
|
98
|
-
|
|
99
|
-
|
|
103
|
+
unknown_key = (Bitcoin.pack_var_int(key_type) + key).bth
|
|
104
|
+
raise ArgumentError, 'Duplicate Key, key for unknown value already provided.' if partial_tx.unknowns[unknown_key]
|
|
105
|
+
partial_tx.unknowns[unknown_key] = value
|
|
100
106
|
end
|
|
101
107
|
end
|
|
102
108
|
|
|
@@ -206,7 +212,6 @@ module Bitcoin
|
|
|
206
212
|
inputs[i].redeem_script = redeem_script if redeem_script
|
|
207
213
|
inputs[i].witness_script = witness_script if witness_script
|
|
208
214
|
inputs[i].hd_key_paths = hd_key_paths.map(&:pubkey).zip(hd_key_paths).to_h
|
|
209
|
-
break
|
|
210
215
|
end
|
|
211
216
|
end
|
|
212
217
|
end
|
|
@@ -267,20 +272,22 @@ module Bitcoin
|
|
|
267
272
|
# extract final tx.
|
|
268
273
|
# @return [Bitcoin::Tx] final tx.
|
|
269
274
|
def extract_tx
|
|
270
|
-
extract_tx = tx.
|
|
275
|
+
extract_tx = Bitcoin::Tx.parse_from_payload(tx.to_payload)
|
|
271
276
|
inputs.each_with_index do |input, index|
|
|
272
277
|
extract_tx.in[index].script_sig = input.final_script_sig if input.final_script_sig
|
|
273
278
|
extract_tx.in[index].script_witness = input.final_script_witness if input.final_script_witness
|
|
274
279
|
end
|
|
280
|
+
# A taproot sighash commits to every prevout of the tx, see BIP-341, so they are all
|
|
281
|
+
# collected before any input is verified.
|
|
282
|
+
prevouts = extract_tx.in.each_with_index.map do |tx_in, index|
|
|
283
|
+
utxo = inputs[index].utxo(tx_in.out_point.index)
|
|
284
|
+
raise ArgumentError, "input[#{index}] does not have utxo." unless utxo
|
|
285
|
+
utxo
|
|
286
|
+
end
|
|
275
287
|
# validate signature
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
utxo = input.non_witness_utxo.out[tx_in.out_point.index]
|
|
280
|
-
raise "input[#{index}]'s signature is invalid.'" unless tx.verify_input_sig(index, utxo.script_pubkey)
|
|
281
|
-
else
|
|
282
|
-
utxo = input.witness_utxo
|
|
283
|
-
raise "input[#{index}]'s signature is invalid.'" unless tx.verify_input_sig(index, utxo.script_pubkey, amount: input.witness_utxo.value)
|
|
288
|
+
prevouts.each_with_index do |utxo, index|
|
|
289
|
+
unless extract_tx.verify_input_sig(index, utxo.script_pubkey, amount: utxo.value, prevouts: prevouts)
|
|
290
|
+
raise "input[#{index}]'s signature is invalid.'"
|
|
284
291
|
end
|
|
285
292
|
end
|
|
286
293
|
extract_tx
|
data/lib/bitcoin/psbt.rb
CHANGED
|
@@ -55,7 +55,7 @@ module Bitcoin
|
|
|
55
55
|
module_function
|
|
56
56
|
|
|
57
57
|
def self.serialize_to_vector(key_type, key: nil, value: nil)
|
|
58
|
-
key_len = key_type.
|
|
58
|
+
key_len = Bitcoin.pack_var_int(key_type).bytesize
|
|
59
59
|
key_len += key.bytesize if key
|
|
60
60
|
s = Bitcoin.pack_var_int(key_len) << Bitcoin.pack_var_int(key_type)
|
|
61
61
|
s << key if key
|
|
@@ -61,8 +61,9 @@ module Bitcoin
|
|
|
61
61
|
request.content_type = 'application/json'
|
|
62
62
|
request.body = data.to_json
|
|
63
63
|
response = http.request(request)
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
# Unescape \uXXXX with JSON.parse itself. Doing it beforehand lets a " contained in
|
|
65
|
+
# a string value close that string and inject arbitrary JSON into the parsed result.
|
|
66
|
+
json_data = JSON.parse(response.body)
|
|
66
67
|
response = convert_floats_to_strings(json_data)
|
|
67
68
|
raise response['error'].to_json if response['error']
|
|
68
69
|
response['result']
|
|
@@ -100,12 +100,22 @@ module Bitcoin
|
|
|
100
100
|
if opcode
|
|
101
101
|
script << (v =~ /^\d/ && Opcodes.small_int_to_opcode(v.ord) ? v.ord : opcode)
|
|
102
102
|
else
|
|
103
|
-
script << (v
|
|
103
|
+
script << (number_token?(v) ? v.to_i : v)
|
|
104
104
|
end
|
|
105
105
|
end
|
|
106
106
|
script
|
|
107
107
|
end
|
|
108
108
|
|
|
109
|
+
# Whether +token+ is a script number rather than pushed data with hex format.
|
|
110
|
+
# A digit only token is ambiguous since #to_s emits pushed data as hex. #to_s never emits
|
|
111
|
+
# a number with more than 10 digits, so a longer even-length token must be pushed data.
|
|
112
|
+
# @param [String] token a token of the script string.
|
|
113
|
+
# @return [Boolean] whether +token+ should be interpreted as a script number.
|
|
114
|
+
def self.number_token?(token)
|
|
115
|
+
return false unless token =~ /^[0-9]+$/
|
|
116
|
+
token.length < 12 || token.length.odd?
|
|
117
|
+
end
|
|
118
|
+
|
|
109
119
|
# generate script from addr.
|
|
110
120
|
# @param [String] addr address.
|
|
111
121
|
# @return [Bitcoin::Script] parsed script.
|
|
@@ -114,7 +124,7 @@ module Bitcoin
|
|
|
114
124
|
segwit_addr = Bech32::SegwitAddr.new(addr)
|
|
115
125
|
raise ArgumentError, 'Invalid address.' unless Bitcoin.chain_params.bech32_hrp == segwit_addr.hrp
|
|
116
126
|
Bitcoin::Script.parse_from_payload(segwit_addr.to_script_pubkey.htb)
|
|
117
|
-
rescue
|
|
127
|
+
rescue StandardError => e
|
|
118
128
|
begin
|
|
119
129
|
hex, addr_version = Bitcoin.decode_base58_address(addr)
|
|
120
130
|
rescue
|
|
@@ -491,11 +501,11 @@ module Bitcoin
|
|
|
491
501
|
size = data.bytesize
|
|
492
502
|
header = if size < OP_PUSHDATA1
|
|
493
503
|
[size].pack('C')
|
|
494
|
-
elsif size
|
|
504
|
+
elsif size <= 0xff
|
|
495
505
|
[OP_PUSHDATA1, size].pack('CC')
|
|
496
|
-
elsif size
|
|
506
|
+
elsif size <= 0xffff
|
|
497
507
|
[OP_PUSHDATA2, size].pack('Cv')
|
|
498
|
-
elsif size
|
|
508
|
+
elsif size <= 0xffffffff
|
|
499
509
|
[OP_PUSHDATA4, size].pack('CV')
|
|
500
510
|
else
|
|
501
511
|
raise ArgumentError, 'data size is too big.'
|
|
@@ -30,7 +30,7 @@ module Bitcoin
|
|
|
30
30
|
begin
|
|
31
31
|
key = Key.new(pubkey: pubkey, key_type: key_type, allow_hybrid: allow_hybrid)
|
|
32
32
|
key.verify(sig, sighash)
|
|
33
|
-
rescue
|
|
33
|
+
rescue StandardError
|
|
34
34
|
false
|
|
35
35
|
end
|
|
36
36
|
end
|
|
@@ -40,9 +40,14 @@ module Bitcoin
|
|
|
40
40
|
# @param [String] pubkey a public key with hex format.
|
|
41
41
|
# @param [Symbol] sig_version whether :taproot or :tapscript
|
|
42
42
|
# @return [Boolean] verification result
|
|
43
|
+
# @raise [ArgumentError] If +prevouts+ does not cover every input of the tx.
|
|
43
44
|
def check_schnorr_sig(sig, pubkey, sig_version, opts = {})
|
|
44
45
|
return false unless [:taproot, :tapscript].include?(sig_version)
|
|
45
|
-
|
|
46
|
+
# A taproot sighash commits to the amount and scriptPubkey of every prevout, so a partial
|
|
47
|
+
# set does not fail to verify, it verifies against a sighash consensus never computes.
|
|
48
|
+
# Checked here rather than left to the generator, since the rescue below would turn it
|
|
49
|
+
# into a script error.
|
|
50
|
+
raise ArgumentError, 'prevouts must be specified for all inputs.' unless prevouts.size == tx.in.size
|
|
46
51
|
|
|
47
52
|
sig = sig.htb
|
|
48
53
|
return set_error(SCRIPT_ERR_SCHNORR_SIG_SIZE) unless [64, 65].include?(sig.bytesize)
|
|
@@ -6,7 +6,8 @@ module Bitcoin
|
|
|
6
6
|
module Secp256k1
|
|
7
7
|
|
|
8
8
|
# binding for secp256k1 (https://github.com/bitcoin-core/secp256k1/)
|
|
9
|
-
# tag: v0.
|
|
9
|
+
# tag: v0.8.0, which secp256k1rb 0.8.0 binds the public API of. An older library is missing
|
|
10
|
+
# symbols the gem attaches, such as secp256k1_ec_pubkey_sort and the musig module.
|
|
10
11
|
# this is not included by default, to enable set shared object path to ENV['SECP256K1_LIB_PATH']
|
|
11
12
|
# for linux, ENV['SECP256K1_LIB_PATH'] = '/usr/local/lib/libsecp256k1.so' or '/usr/lib64/libsecp256k1.so'
|
|
12
13
|
# for mac,
|
|
@@ -98,6 +99,54 @@ module Bitcoin
|
|
|
98
99
|
raise ArgumentError, "unknown algo: #{algo}"
|
|
99
100
|
end
|
|
100
101
|
end
|
|
102
|
+
|
|
103
|
+
# Whether the loaded library supports BIP-352 silent payments.
|
|
104
|
+
# The module exists in libsecp256k1 v0.8.0 or later.
|
|
105
|
+
# @return [Boolean]
|
|
106
|
+
def sp_available?
|
|
107
|
+
silentpayments_available?
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Create the silent payment outputs for +recipients+.
|
|
111
|
+
# @param [Array] recipients An array of [scan public key, spend public key], both with hex
|
|
112
|
+
# format(33 bytes). A recipient may appear more than once.
|
|
113
|
+
# @param [String] outpoint_smallest The lexicographically smallest outpoint of the tx inputs
|
|
114
|
+
# with hex format(36 bytes).
|
|
115
|
+
# @param [Array] plain_seckeys Private keys of the non-taproot inputs with hex format.
|
|
116
|
+
# @param [Array] taproot_seckeys Private keys of the taproot inputs with hex format.
|
|
117
|
+
# @return [Array] An x-only public key with hex format for each recipient, in the same order.
|
|
118
|
+
# @raise [Secp256k1::Error] If the outputs could not be created.
|
|
119
|
+
def sp_create_outputs(recipients, outpoint_smallest, plain_seckeys: [], taproot_seckeys: [])
|
|
120
|
+
silentpayments_sender_create_outputs(
|
|
121
|
+
recipients, outpoint_smallest, plain_seckeys: plain_seckeys, taproot_seckeys: taproot_seckeys)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Create the label and the label tweak of the +m+ th label of +scan_key+.
|
|
125
|
+
# @param [String] scan_key The recipient's scan private key with hex format(32 bytes).
|
|
126
|
+
# @param [Integer] m The label index.
|
|
127
|
+
# @return [Array] The serialized label(33 bytes) and its tweak(32 bytes), both with hex format.
|
|
128
|
+
def sp_create_label(scan_key, m)
|
|
129
|
+
silentpayments_create_label(scan_key, m)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Scan +tx_outputs+ for the silent payment outputs of the recipient.
|
|
133
|
+
# @param [Array] tx_outputs The x-only public key of each taproot output with hex format.
|
|
134
|
+
# @param [String] scan_key The recipient's scan private key with hex format(32 bytes).
|
|
135
|
+
# @param [String] outpoint_smallest The lexicographically smallest outpoint of the tx inputs
|
|
136
|
+
# with hex format(36 bytes).
|
|
137
|
+
# @param [String] spend_pubkey The recipient's spend public key with hex format(33 bytes).
|
|
138
|
+
# @param [Array] plain_pubkeys Public keys of the non-taproot inputs with hex format(33 bytes).
|
|
139
|
+
# @param [Array] xonly_pubkeys X-only public keys of the taproot inputs with hex format.
|
|
140
|
+
# @param [Hash] labels A serialized label to label tweak map, both with hex format.
|
|
141
|
+
# @return [Array] A hash per found output, with the :output, :tweak and :label keys.
|
|
142
|
+
# @raise [Secp256k1::Error] If the tx is not a silent payment transaction.
|
|
143
|
+
def sp_scan_outputs(tx_outputs, scan_key, outpoint_smallest, spend_pubkey,
|
|
144
|
+
plain_pubkeys: [], xonly_pubkeys: [], labels: {})
|
|
145
|
+
summary = silentpayments_create_prevouts_summary(
|
|
146
|
+
outpoint_smallest, plain_pubkeys: plain_pubkeys, xonly_pubkeys: xonly_pubkeys)
|
|
147
|
+
silentpayments_scan_outputs(
|
|
148
|
+
tx_outputs, scan_key, summary, spend_pubkey, labels: labels.empty? ? nil : labels)
|
|
149
|
+
end
|
|
101
150
|
end
|
|
102
151
|
end
|
|
103
152
|
end
|
|
@@ -26,10 +26,13 @@ module Bitcoin
|
|
|
26
26
|
# 3.2.g
|
|
27
27
|
v = Bitcoin.hmac_sha256(k, v)
|
|
28
28
|
# 3.2.h
|
|
29
|
-
t = ''
|
|
30
29
|
10000.times do
|
|
31
|
-
|
|
32
|
-
t =
|
|
30
|
+
# T is reset for each candidate, otherwise a retry can never produce a value below the order.
|
|
31
|
+
t = ''
|
|
32
|
+
while t.bytesize < 32
|
|
33
|
+
v = Bitcoin.hmac_sha256(k, v)
|
|
34
|
+
t = (t + v)
|
|
35
|
+
end
|
|
33
36
|
t_num = t.bth.to_i(16)
|
|
34
37
|
return t_num if 1 <= t_num && t_num < Bitcoin::Secp256k1::GROUP.order
|
|
35
38
|
k = Bitcoin.hmac_sha256(k, v + '00'.htb)
|
|
@@ -45,7 +45,7 @@ module Bitcoin
|
|
|
45
45
|
return false unless pubkey.bytesize == X_ONLY_PUBKEY_SIZE
|
|
46
46
|
begin
|
|
47
47
|
ECDSA::Format::PointOctetString.decode(pubkey, ECDSA::Group::Secp256k1)
|
|
48
|
-
rescue
|
|
48
|
+
rescue StandardError
|
|
49
49
|
return false
|
|
50
50
|
end
|
|
51
51
|
true
|
|
@@ -201,7 +201,7 @@ module Bitcoin
|
|
|
201
201
|
k = ECDSA::Format::PointOctetString.decode(repack_pubkey(pubkey), GROUP)
|
|
202
202
|
signature = ECDSA::Format::SignatureDerString.decode(sig)
|
|
203
203
|
ECDSA.valid_signature?(k, data, signature)
|
|
204
|
-
rescue
|
|
204
|
+
rescue StandardError
|
|
205
205
|
false
|
|
206
206
|
end
|
|
207
207
|
end
|
|
@@ -210,6 +210,119 @@ module Bitcoin
|
|
|
210
210
|
Schnorr.valid_sig?(data, pubkey.htb, sig)
|
|
211
211
|
end
|
|
212
212
|
|
|
213
|
+
# Whether this module supports BIP-352 silent payments.
|
|
214
|
+
# @return [Boolean]
|
|
215
|
+
def sp_available?
|
|
216
|
+
true
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Create the silent payment outputs for +recipients+.
|
|
220
|
+
# See Bitcoin::Secp256k1::Native#sp_create_outputs for the parameters.
|
|
221
|
+
# @return [Array] An x-only public key with hex format for each recipient, in the same order.
|
|
222
|
+
# @raise [ArgumentError] If the input private keys sum to zero.
|
|
223
|
+
def sp_create_outputs(recipients, outpoint_smallest, plain_seckeys: [], taproot_seckeys: [])
|
|
224
|
+
field = ECDSA::PrimeField.new(GROUP.order)
|
|
225
|
+
sum = sp_sum_seckeys(plain_seckeys, taproot_seckeys, field)
|
|
226
|
+
raise ArgumentError, 'The input private keys sum to zero.' if sum.zero?
|
|
227
|
+
agg_pubkey = (GROUP.generator.to_jacobian * sum).to_affine
|
|
228
|
+
input_hash = Bitcoin.tagged_hash('BIP0352/Inputs', outpoint_smallest.htb + agg_pubkey.to_hex.htb)
|
|
229
|
+
|
|
230
|
+
# k counts up within the group of recipients sharing a scan key, but an output keeps the
|
|
231
|
+
# position of the recipient it pays.
|
|
232
|
+
groups = {}
|
|
233
|
+
recipients.each_with_index do |(scan_pubkey, spend_pubkey), index|
|
|
234
|
+
(groups[scan_pubkey] ||= []) << [spend_pubkey, index]
|
|
235
|
+
end
|
|
236
|
+
results = Array.new(recipients.length)
|
|
237
|
+
groups.each do |scan_pubkey, spends|
|
|
238
|
+
scan_point = Bitcoin::Key.new(pubkey: scan_pubkey).to_point.to_jacobian
|
|
239
|
+
shared_secret = (scan_point * field.mod(input_hash.bti * sum)).to_affine.to_hex.htb
|
|
240
|
+
spends.each_with_index do |(spend_pubkey, index), k|
|
|
241
|
+
t_k = Bitcoin.tagged_hash('BIP0352/SharedSecret', shared_secret + [k].pack('N'))
|
|
242
|
+
spend_point = Bitcoin::Key.new(pubkey: spend_pubkey).to_point.to_jacobian
|
|
243
|
+
output = (spend_point + GROUP.generator.to_jacobian * t_k.bti).to_affine
|
|
244
|
+
results[index] = sp_xonly(output.x)
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
results
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Create the label and the label tweak of the +m+ th label of +scan_key+.
|
|
251
|
+
# See Bitcoin::Secp256k1::Native#sp_create_label for the parameters.
|
|
252
|
+
# @return [Array] The serialized label(33 bytes) and its tweak(32 bytes), both with hex format.
|
|
253
|
+
def sp_create_label(scan_key, m)
|
|
254
|
+
tweak = Bitcoin.tagged_hash('BIP0352/Label', scan_key.htb + [m].pack('N'))
|
|
255
|
+
[(GROUP.generator.to_jacobian * tweak.bti).to_affine.to_hex(true), tweak.bth]
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Scan +tx_outputs+ for the silent payment outputs of the recipient.
|
|
259
|
+
# See Bitcoin::Secp256k1::Native#sp_scan_outputs for the parameters.
|
|
260
|
+
# @return [Array] A hash per found output, with the :output, :tweak and :label keys.
|
|
261
|
+
# @raise [ArgumentError] If the input public keys sum to the point at infinity.
|
|
262
|
+
def sp_scan_outputs(tx_outputs, scan_key, outpoint_smallest, spend_pubkey,
|
|
263
|
+
plain_pubkeys: [], xonly_pubkeys: [], labels: {})
|
|
264
|
+
field = ECDSA::PrimeField.new(GROUP.order)
|
|
265
|
+
sum_pubkeys = GROUP.infinity.to_jacobian
|
|
266
|
+
plain_pubkeys.each { |p| sum_pubkeys += Bitcoin::Key.new(pubkey: p).to_point.to_jacobian }
|
|
267
|
+
xonly_pubkeys.each { |p| sum_pubkeys += Bitcoin::Key.from_xonly_pubkey(p).to_point.to_jacobian }
|
|
268
|
+
raise ArgumentError, 'The input public keys sum to the point at infinity.' if sum_pubkeys.infinity?
|
|
269
|
+
|
|
270
|
+
input_hash = Bitcoin.tagged_hash(
|
|
271
|
+
'BIP0352/Inputs', outpoint_smallest.htb + sum_pubkeys.to_affine.to_hex.htb)
|
|
272
|
+
shared_secret = (sum_pubkeys * field.mod(input_hash.bti * scan_key.to_i(16))).to_affine.to_hex.htb
|
|
273
|
+
spend_point = Bitcoin::Key.new(pubkey: spend_pubkey).to_point.to_jacobian
|
|
274
|
+
# A labeled output is P_k + label. Only the x coordinate is compared, which covers the
|
|
275
|
+
# label of either parity without negating the output.
|
|
276
|
+
label_points = labels.map do |label, tweak|
|
|
277
|
+
[label, tweak, Bitcoin::Key.new(pubkey: label).to_point.to_jacobian]
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
results = []
|
|
281
|
+
remaining = tx_outputs.map(&:downcase)
|
|
282
|
+
k = 0
|
|
283
|
+
while k < Bitcoin::SilentPayment::K_MAX
|
|
284
|
+
t_k = Bitcoin.tagged_hash('BIP0352/SharedSecret', shared_secret + [k].pack('N'))
|
|
285
|
+
p_k = GROUP.generator.to_jacobian * t_k.bti + spend_point
|
|
286
|
+
index = remaining.index(sp_xonly(p_k.to_affine.x))
|
|
287
|
+
found = if index
|
|
288
|
+
{output: remaining.delete_at(index), tweak: t_k.bth, label: nil}
|
|
289
|
+
else
|
|
290
|
+
sp_find_labeled(p_k, remaining, label_points, t_k, field)
|
|
291
|
+
end
|
|
292
|
+
break unless found
|
|
293
|
+
results << found
|
|
294
|
+
k += 1
|
|
295
|
+
end
|
|
296
|
+
results
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Sum the private keys of the inputs, negating a taproot key whose public key has odd y.
|
|
300
|
+
def sp_sum_seckeys(plain_seckeys, taproot_seckeys, field)
|
|
301
|
+
sum = plain_seckeys.inject(0) { |total, sk| field.mod(total + sk.to_i(16)) }
|
|
302
|
+
taproot_seckeys.inject(sum) do |total, sk|
|
|
303
|
+
d = sk.to_i(16)
|
|
304
|
+
d = field.mod(-d) unless (GROUP.generator.to_jacobian * d).to_affine.has_even_y?
|
|
305
|
+
field.mod(total + d)
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Find the output +p_k+ pays through one of +label_points+, or nil.
|
|
310
|
+
def sp_find_labeled(p_k, remaining, label_points, t_k, field)
|
|
311
|
+
label_points.each do |label, tweak, point|
|
|
312
|
+
index = remaining.index(sp_xonly((p_k + point).to_affine.x))
|
|
313
|
+
next unless index
|
|
314
|
+
return {output: remaining.delete_at(index),
|
|
315
|
+
tweak: sp_xonly(field.mod(t_k.bti + tweak.to_i(16))),
|
|
316
|
+
label: label}
|
|
317
|
+
end
|
|
318
|
+
nil
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Serialize a field element as a 32 byte value with hex format.
|
|
322
|
+
def sp_xonly(value)
|
|
323
|
+
ECDSA::Format::IntegerOctetString.encode(value, 32).bth
|
|
324
|
+
end
|
|
325
|
+
|
|
213
326
|
end
|
|
214
327
|
end
|
|
215
328
|
end
|