bitcoinrb 0.5.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +37 -0
- data/.rspec_parallel +2 -0
- data/.ruby-version +1 -1
- data/README.md +11 -1
- data/bitcoinrb.gemspec +7 -6
- data/lib/bitcoin/block_filter.rb +14 -0
- data/lib/bitcoin/chain_params.rb +9 -0
- data/lib/bitcoin/chainparams/signet.yml +39 -0
- data/lib/bitcoin/constants.rb +45 -4
- data/lib/bitcoin/descriptor.rb +1 -1
- data/lib/bitcoin/errors.rb +19 -0
- data/lib/bitcoin/ext/array_ext.rb +22 -0
- data/lib/bitcoin/ext/ecdsa.rb +36 -0
- data/lib/bitcoin/ext.rb +1 -0
- data/lib/bitcoin/ext_key.rb +36 -20
- data/lib/bitcoin/key.rb +85 -28
- data/lib/bitcoin/message/addr_v2.rb +34 -0
- data/lib/bitcoin/message/base.rb +16 -0
- data/lib/bitcoin/message/cfcheckpt.rb +2 -2
- data/lib/bitcoin/message/cfheaders.rb +1 -1
- data/lib/bitcoin/message/cfilter.rb +1 -1
- data/lib/bitcoin/message/fee_filter.rb +1 -1
- data/lib/bitcoin/message/filter_load.rb +3 -3
- data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
- data/lib/bitcoin/message/inventory.rb +1 -1
- data/lib/bitcoin/message/merkle_block.rb +1 -1
- data/lib/bitcoin/message/network_addr.rb +141 -18
- data/lib/bitcoin/message/ping.rb +1 -1
- data/lib/bitcoin/message/pong.rb +1 -1
- data/lib/bitcoin/message/send_addr_v2.rb +13 -0
- data/lib/bitcoin/message/send_cmpct.rb +2 -2
- data/lib/bitcoin/message/tx.rb +1 -1
- data/lib/bitcoin/message.rb +72 -0
- data/lib/bitcoin/message_sign.rb +47 -0
- data/lib/bitcoin/mnemonic.rb +2 -2
- data/lib/bitcoin/network/peer_discovery.rb +1 -3
- data/lib/bitcoin/node/configuration.rb +3 -1
- data/lib/bitcoin/node/spv.rb +8 -0
- data/lib/bitcoin/opcodes.rb +14 -1
- data/lib/bitcoin/payment_code.rb +2 -2
- data/lib/bitcoin/payments/payment.pb.rb +1 -1
- data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
- data/lib/bitcoin/psbt/input.rb +4 -4
- data/lib/bitcoin/psbt/output.rb +1 -1
- data/lib/bitcoin/psbt/tx.rb +14 -5
- data/lib/bitcoin/psbt.rb +8 -0
- data/lib/bitcoin/rpc/bitcoin_core_client.rb +1 -1
- data/lib/bitcoin/rpc/request_handler.rb +3 -3
- data/lib/bitcoin/script/script.rb +80 -30
- data/lib/bitcoin/script/script_error.rb +27 -1
- data/lib/bitcoin/script/script_interpreter.rb +164 -62
- data/lib/bitcoin/script/tx_checker.rb +62 -14
- data/lib/bitcoin/secp256k1/native.rb +184 -17
- data/lib/bitcoin/secp256k1/ruby.rb +108 -21
- data/lib/bitcoin/sighash_generator.rb +157 -0
- data/lib/bitcoin/taproot/leaf_node.rb +23 -0
- data/lib/bitcoin/taproot/simple_builder.rb +155 -0
- data/lib/bitcoin/taproot.rb +45 -0
- data/lib/bitcoin/tx.rb +30 -96
- data/lib/bitcoin/tx_in.rb +1 -1
- data/lib/bitcoin/tx_out.rb +2 -3
- data/lib/bitcoin/util.rb +15 -6
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet/account.rb +1 -1
- data/lib/bitcoin.rb +32 -24
- metadata +58 -18
- data/.travis.yml +0 -12
@@ -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,
|
@@ -50,6 +50,14 @@ module Bitcoin
|
|
50
50
|
attach_function(:secp256k1_ecdsa_signature_parse_der, [:pointer, :pointer, :pointer, :size_t], :int)
|
51
51
|
attach_function(:secp256k1_ecdsa_signature_normalize, [:pointer, :pointer, :pointer], :int)
|
52
52
|
attach_function(:secp256k1_ecdsa_verify, [:pointer, :pointer, :pointer, :pointer], :int)
|
53
|
+
attach_function(:secp256k1_schnorrsig_sign, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
|
54
|
+
attach_function(:secp256k1_schnorrsig_verify, [:pointer, :pointer, :pointer, :pointer], :int)
|
55
|
+
attach_function(:secp256k1_keypair_create, [:pointer, :pointer, :pointer], :int)
|
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)
|
53
61
|
end
|
54
62
|
|
55
63
|
def with_context(flags: (SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN))
|
@@ -97,13 +105,152 @@ module Bitcoin
|
|
97
105
|
|
98
106
|
# sign data.
|
99
107
|
# @param [String] data a data to be signed with binary format
|
100
|
-
# @param [String] privkey a private key using sign
|
101
|
-
# @param [String] extra_entropy a extra entropy for rfc6979
|
108
|
+
# @param [String] privkey a private key with hex format using sign
|
109
|
+
# @param [String] extra_entropy a extra entropy with binary format for rfc6979
|
110
|
+
# @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
|
102
111
|
# @return [String] signature data with binary format
|
103
|
-
def sign_data(data, privkey, extra_entropy)
|
112
|
+
def sign_data(data, privkey, extra_entropy = nil, algo: :ecdsa)
|
113
|
+
case algo
|
114
|
+
when :ecdsa
|
115
|
+
sign_ecdsa(data, privkey, extra_entropy)
|
116
|
+
when :schnorr
|
117
|
+
sign_schnorr(data, privkey, extra_entropy)
|
118
|
+
else
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
end
|
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
|
+
|
169
|
+
# verify signature
|
170
|
+
# @param [String] data a data with binary format.
|
171
|
+
# @param [String] sig signature data with binary format
|
172
|
+
# @param [String] pubkey a public key with hex format using verify.
|
173
|
+
# # @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
|
174
|
+
# @return [Boolean] verification result.
|
175
|
+
def verify_sig(data, sig, pubkey, algo: :ecdsa)
|
176
|
+
case algo
|
177
|
+
when :ecdsa
|
178
|
+
verify_ecdsa(data, sig, pubkey)
|
179
|
+
when :schnorr
|
180
|
+
verify_schnorr(data, sig, pubkey)
|
181
|
+
else
|
182
|
+
false
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# # validate whether this is a valid public key (more expensive than IsValid())
|
187
|
+
# @param [String] pub_key public key with hex format.
|
188
|
+
# @param [Boolean] allow_hybrid whether support hybrid public key.
|
189
|
+
# @return [Boolean] If valid public key return true, otherwise false.
|
190
|
+
def parse_ec_pubkey?(pub_key, allow_hybrid = false)
|
191
|
+
pub_key = pub_key.htb
|
192
|
+
return false if !allow_hybrid && ![0x02, 0x03, 0x04].include?(pub_key[0].ord)
|
193
|
+
with_context do |context|
|
194
|
+
pubkey = FFI::MemoryPointer.new(:uchar, pub_key.bytesize).put_bytes(0, pub_key)
|
195
|
+
internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
|
196
|
+
result = secp256k1_ec_pubkey_parse(context, internal_pubkey, pubkey, pub_key.bytesize)
|
197
|
+
result == 1
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Create key pair data from private key.
|
202
|
+
# @param [String] priv_key with hex format
|
203
|
+
# @return [String] key pair data with hex format. data = private key(32 bytes) | public key(64 bytes).
|
204
|
+
def create_keypair(priv_key)
|
205
|
+
with_context do |context|
|
206
|
+
priv_key = priv_key.htb
|
207
|
+
secret = FFI::MemoryPointer.new(:uchar, priv_key.bytesize).put_bytes(0, priv_key)
|
208
|
+
raise 'priv_key is invalid.' unless secp256k1_ec_seckey_verify(context, secret)
|
209
|
+
keypair = FFI::MemoryPointer.new(:uchar, 96)
|
210
|
+
raise 'priv_key is invalid.' unless secp256k1_keypair_create(context, keypair, secret) == 1
|
211
|
+
keypair.read_string(96).bth
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Check whether valid x-only public key or not.
|
216
|
+
# @param [String] pub_key x-only public key with hex format(32 bytes).
|
217
|
+
# @return [Boolean] result.
|
218
|
+
def valid_xonly_pubkey?(pub_key)
|
219
|
+
begin
|
220
|
+
full_pubkey_from_xonly_pubkey(pub_key)
|
221
|
+
rescue Exception
|
222
|
+
return false
|
223
|
+
end
|
224
|
+
true
|
225
|
+
end
|
226
|
+
|
227
|
+
private
|
228
|
+
|
229
|
+
# Calculate full public key(64 bytes) from public key(32 bytes).
|
230
|
+
# @param [String] pub_key x-only public key with hex format(32 bytes).
|
231
|
+
# @return [String] x-only public key with hex format(64 bytes).
|
232
|
+
def full_pubkey_from_xonly_pubkey(pub_key)
|
233
|
+
with_context do |context|
|
234
|
+
pubkey = pub_key.htb
|
235
|
+
raise ArgumentError, 'Pubkey size must be 32 bytes.' unless pubkey.bytesize == 32
|
236
|
+
xonly_pubkey = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
|
237
|
+
full_pubkey = FFI::MemoryPointer.new(:uchar, 64)
|
238
|
+
raise ArgumentError, 'An invalid public key was specified.' unless secp256k1_xonly_pubkey_parse(context, full_pubkey, xonly_pubkey) == 1
|
239
|
+
full_pubkey.read_string(64).bth
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def generate_pubkey_in_context(context, privkey, compressed: true)
|
244
|
+
internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
|
245
|
+
result = secp256k1_ec_pubkey_create(context, internal_pubkey, privkey.htb)
|
246
|
+
raise 'error creating pubkey' unless result
|
247
|
+
serialize_pubkey_internal(context, internal_pubkey, compressed)
|
248
|
+
end
|
249
|
+
|
250
|
+
def sign_ecdsa(data, privkey, extra_entropy)
|
104
251
|
with_context do |context|
|
105
252
|
secret = FFI::MemoryPointer.new(:uchar, privkey.htb.bytesize).put_bytes(0, privkey.htb)
|
106
|
-
raise 'priv_key invalid' unless secp256k1_ec_seckey_verify(context, secret)
|
253
|
+
raise 'priv_key is invalid' unless secp256k1_ec_seckey_verify(context, secret)
|
107
254
|
|
108
255
|
internal_signature = FFI::MemoryPointer.new(:uchar, 64)
|
109
256
|
msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
|
@@ -126,11 +273,23 @@ module Bitcoin
|
|
126
273
|
end
|
127
274
|
end
|
128
275
|
|
129
|
-
def
|
276
|
+
def sign_schnorr(data, privkey, aux_rand = nil)
|
130
277
|
with_context do |context|
|
131
|
-
|
278
|
+
keypair = create_keypair(privkey).htb
|
279
|
+
keypair = FFI::MemoryPointer.new(:uchar, 96).put_bytes(0, keypair)
|
280
|
+
signature = FFI::MemoryPointer.new(:uchar, 64)
|
281
|
+
msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
|
282
|
+
aux_rand = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, aux_rand) if aux_rand
|
283
|
+
raise 'Failed to generate schnorr signature.' unless secp256k1_schnorrsig_sign(context, signature, msg32, keypair, nil, aux_rand) == 1
|
284
|
+
signature.read_string(64)
|
285
|
+
end
|
286
|
+
end
|
132
287
|
|
133
|
-
|
288
|
+
def verify_ecdsa(data, sig, pubkey)
|
289
|
+
with_context do |context|
|
290
|
+
return false if data.bytesize == 0
|
291
|
+
pubkey = pubkey.htb
|
292
|
+
pubkey = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
|
134
293
|
internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
|
135
294
|
result = secp256k1_ec_pubkey_parse(context, internal_pubkey, pubkey, pubkey.size)
|
136
295
|
return false unless result
|
@@ -150,25 +309,33 @@ module Bitcoin
|
|
150
309
|
end
|
151
310
|
end
|
152
311
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
312
|
+
def verify_schnorr(data, sig, pubkey)
|
313
|
+
with_context do |context|
|
314
|
+
return false if data.bytesize == 0
|
315
|
+
pubkey = full_pubkey_from_xonly_pubkey(pubkey).htb
|
316
|
+
xonly_pubkey = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
|
317
|
+
signature = FFI::MemoryPointer.new(:uchar, sig.bytesize).put_bytes(0, sig)
|
318
|
+
msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
|
319
|
+
result = secp256k1_schnorrsig_verify(context, signature, msg32, xonly_pubkey)
|
320
|
+
result == 1
|
321
|
+
end
|
322
|
+
end
|
159
323
|
|
324
|
+
# Serialize public key.
|
325
|
+
def serialize_pubkey_internal(context, pubkey_input, compressed)
|
160
326
|
pubkey = FFI::MemoryPointer.new(:uchar, 65)
|
161
327
|
pubkey_len = FFI::MemoryPointer.new(:uint64)
|
162
328
|
result = if compressed
|
163
329
|
pubkey_len.put_uint64(0, 33)
|
164
|
-
secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len,
|
330
|
+
secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, pubkey_input, SECP256K1_EC_COMPRESSED)
|
165
331
|
else
|
166
332
|
pubkey_len.put_uint64(0, 65)
|
167
|
-
secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len,
|
333
|
+
secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, pubkey_input, SECP256K1_EC_UNCOMPRESSED)
|
168
334
|
end
|
169
335
|
raise 'error serialize pubkey' unless result || pubkey_len.read_uint64 > 0
|
170
336
|
pubkey.read_string(pubkey_len.read_uint64).bth
|
171
337
|
end
|
338
|
+
|
172
339
|
end
|
173
340
|
end
|
174
341
|
end
|
@@ -27,11 +27,105 @@ module Bitcoin
|
|
27
27
|
public_key.to_hex(compressed)
|
28
28
|
end
|
29
29
|
|
30
|
+
# Check whether valid x-only public key or not.
|
31
|
+
# @param [String] pub_key x-only public key with hex format(32 bytes).
|
32
|
+
# @return [Boolean] result.
|
33
|
+
def valid_xonly_pubkey?(pub_key)
|
34
|
+
pubkey = pub_key.htb
|
35
|
+
return false unless pubkey.bytesize == 32
|
36
|
+
begin
|
37
|
+
ECDSA::Format::PointOctetString.decode(pubkey, ECDSA::Group::Secp256k1)
|
38
|
+
rescue Exception
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
30
44
|
# sign data.
|
31
45
|
# @param [String] data a data to be signed with binary format
|
32
|
-
# @param [String] privkey a private key using sign
|
46
|
+
# @param [String] privkey a private key using sign with hex format
|
47
|
+
# @param [String] extra_entropy a extra entropy with binary format for rfc6979
|
48
|
+
# @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
|
33
49
|
# @return [String] signature data with binary format
|
34
|
-
def sign_data(data, privkey, extra_entropy)
|
50
|
+
def sign_data(data, privkey, extra_entropy = nil, algo: :ecdsa)
|
51
|
+
case algo
|
52
|
+
when :ecdsa
|
53
|
+
sign_ecdsa(data, privkey, extra_entropy)&.first
|
54
|
+
when :schnorr
|
55
|
+
sign_schnorr(data, privkey, extra_entropy)
|
56
|
+
else
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
end
|
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
|
+
|
86
|
+
# verify signature using public key
|
87
|
+
# @param [String] data a SHA-256 message digest with binary format
|
88
|
+
# @param [String] sig a signature for +data+ with binary format
|
89
|
+
# @param [String] pubkey a public key with hex format.
|
90
|
+
# @return [Boolean] verify result
|
91
|
+
def verify_sig(data, sig, pubkey, algo: :ecdsa)
|
92
|
+
case algo
|
93
|
+
when :ecdsa
|
94
|
+
verify_ecdsa(data, sig, pubkey)
|
95
|
+
when :schnorr
|
96
|
+
verify_schnorr(data, sig, pubkey)
|
97
|
+
else
|
98
|
+
false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# if +pubkey+ is hybrid public key format, it convert uncompressed format.
|
103
|
+
# https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2012-June/001578.html
|
104
|
+
def repack_pubkey(pubkey)
|
105
|
+
p = pubkey.htb
|
106
|
+
case p[0]
|
107
|
+
when "\x06", "\x07"
|
108
|
+
p[0] = "\x04"
|
109
|
+
p
|
110
|
+
else
|
111
|
+
pubkey.htb
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# validate whether this is a valid public key (more expensive than IsValid())
|
116
|
+
# @param [String] pubkey public key with hex format.
|
117
|
+
# @param [Boolean] allow_hybrid whether support hybrid public key.
|
118
|
+
# @return [Boolean] If valid public key return true, otherwise false.
|
119
|
+
def parse_ec_pubkey?(pubkey, allow_hybrid = false)
|
120
|
+
begin
|
121
|
+
point = ECDSA::Format::PointOctetString.decode(pubkey.htb, ECDSA::Group::Secp256k1, allow_hybrid: allow_hybrid)
|
122
|
+
ECDSA::Group::Secp256k1.valid_public_key?(point)
|
123
|
+
rescue ECDSA::Format::DecodeError
|
124
|
+
false
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def sign_ecdsa(data, privkey, extra_entropy)
|
35
129
|
privkey = privkey.htb
|
36
130
|
private_key = ECDSA::Format::IntegerOctetString.decode(privkey)
|
37
131
|
extra_entropy ||= ''
|
@@ -44,11 +138,14 @@ module Bitcoin
|
|
44
138
|
r = point_field.mod(r_point.x)
|
45
139
|
return nil if r.zero?
|
46
140
|
|
141
|
+
rec = r_point.y & 1
|
142
|
+
|
47
143
|
e = ECDSA.normalize_digest(data, GROUP.bit_length)
|
48
144
|
s = point_field.mod(point_field.inverse(nonce) * (e + r * private_key))
|
49
145
|
|
50
146
|
if s > (GROUP.order / 2) # convert low-s
|
51
147
|
s = GROUP.order - s
|
148
|
+
rec ^= 1
|
52
149
|
end
|
53
150
|
|
54
151
|
return nil if s.zero?
|
@@ -56,35 +153,25 @@ module Bitcoin
|
|
56
153
|
signature = ECDSA::Signature.new(r, s).to_der
|
57
154
|
public_key = Bitcoin::Key.new(priv_key: privkey.bth).pubkey
|
58
155
|
raise 'Creation of signature failed.' unless Bitcoin::Secp256k1::Ruby.verify_sig(data, signature, public_key)
|
59
|
-
signature
|
156
|
+
[signature, rec]
|
60
157
|
end
|
61
158
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
def verify_sig(digest, sig, pubkey)
|
159
|
+
def sign_schnorr(data, privkey, aux_rand)
|
160
|
+
aux_rand ? Schnorr.sign(data, privkey.htb, aux_rand).encode : Schnorr.sign(data, privkey.htb).encode
|
161
|
+
end
|
162
|
+
|
163
|
+
def verify_ecdsa(data, sig, pubkey)
|
68
164
|
begin
|
69
165
|
k = ECDSA::Format::PointOctetString.decode(repack_pubkey(pubkey), GROUP)
|
70
166
|
signature = ECDSA::Format::SignatureDerString.decode(sig)
|
71
|
-
ECDSA.valid_signature?(k,
|
167
|
+
ECDSA.valid_signature?(k, data, signature)
|
72
168
|
rescue Exception
|
73
169
|
false
|
74
170
|
end
|
75
171
|
end
|
76
172
|
|
77
|
-
|
78
|
-
|
79
|
-
def repack_pubkey(pubkey)
|
80
|
-
p = pubkey.htb
|
81
|
-
case p[0]
|
82
|
-
when "\x06", "\x07"
|
83
|
-
p[0] = "\x04"
|
84
|
-
p
|
85
|
-
else
|
86
|
-
pubkey.htb
|
87
|
-
end
|
173
|
+
def verify_schnorr(data, sig, pubkey)
|
174
|
+
Schnorr.valid_sig?(data, pubkey.htb, sig)
|
88
175
|
end
|
89
176
|
|
90
177
|
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
|
3
|
+
module SigHashGenerator
|
4
|
+
|
5
|
+
def self.load(sig_ver)
|
6
|
+
case sig_ver
|
7
|
+
when :base
|
8
|
+
LegacySigHashGenerator.new
|
9
|
+
when :witness_v0
|
10
|
+
SegwitSigHashGenerator.new
|
11
|
+
when :taproot, :tapscript
|
12
|
+
SchnorrSigHashGenerator.new
|
13
|
+
else
|
14
|
+
raise ArgumentError, "Unsupported sig version specified. #{sig_ver}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Legacy SigHash Generator
|
19
|
+
class LegacySigHashGenerator
|
20
|
+
|
21
|
+
def generate(tx, input_index, hash_type, opts)
|
22
|
+
output_script = opts[:script_code]
|
23
|
+
ins = tx.inputs.map.with_index do |i, idx|
|
24
|
+
if idx == input_index
|
25
|
+
i.to_payload(output_script.delete_opcode(Bitcoin::Opcodes::OP_CODESEPARATOR))
|
26
|
+
else
|
27
|
+
case hash_type & 0x1f
|
28
|
+
when SIGHASH_TYPE[:none], SIGHASH_TYPE[:single]
|
29
|
+
i.to_payload(Bitcoin::Script.new, 0)
|
30
|
+
else
|
31
|
+
i.to_payload(Bitcoin::Script.new)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
outs = tx.outputs.map(&:to_payload)
|
37
|
+
out_size = Bitcoin.pack_var_int(tx.outputs.size)
|
38
|
+
|
39
|
+
case hash_type & 0x1f
|
40
|
+
when SIGHASH_TYPE[:none]
|
41
|
+
outs = ''
|
42
|
+
out_size = Bitcoin.pack_var_int(0)
|
43
|
+
when SIGHASH_TYPE[:single]
|
44
|
+
return "\x01".ljust(32, "\x00") if input_index >= tx.outputs.size
|
45
|
+
outs = tx.outputs[0...(input_index + 1)].map.with_index { |o, idx| (idx == input_index) ? o.to_payload : o.to_empty_payload }.join
|
46
|
+
out_size = Bitcoin.pack_var_int(input_index + 1)
|
47
|
+
end
|
48
|
+
|
49
|
+
ins = [ins[input_index]] unless hash_type & SIGHASH_TYPE[:anyonecanpay] == 0
|
50
|
+
|
51
|
+
buf = [[tx.version].pack('V'), Bitcoin.pack_var_int(ins.size),
|
52
|
+
ins, out_size, outs, [tx.lock_time, hash_type].pack('VV')].join
|
53
|
+
|
54
|
+
Bitcoin.double_sha256(buf)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
# V0 witness sighash generator.
|
60
|
+
# see: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
|
61
|
+
class SegwitSigHashGenerator
|
62
|
+
|
63
|
+
def generate(tx, input_index, hash_type, opts)
|
64
|
+
amount = opts[:amount]
|
65
|
+
raise ArgumentError, 'segwit sighash requires amount.' unless amount
|
66
|
+
output_script = opts[:script_code]
|
67
|
+
skip_separator_index = opts[:skip_separator_index]
|
68
|
+
hash_prevouts = Bitcoin.double_sha256(tx.inputs.map{|i|i.out_point.to_payload}.join)
|
69
|
+
hash_sequence = Bitcoin.double_sha256(tx.inputs.map{|i|[i.sequence].pack('V')}.join)
|
70
|
+
outpoint = tx.inputs[input_index].out_point.to_payload
|
71
|
+
amount = [amount].pack('Q')
|
72
|
+
nsequence = [tx.inputs[input_index].sequence].pack('V')
|
73
|
+
hash_outputs = Bitcoin.double_sha256(tx.outputs.map{|o|o.to_payload}.join)
|
74
|
+
|
75
|
+
script_code = output_script.to_script_code(skip_separator_index)
|
76
|
+
|
77
|
+
case (hash_type & 0x1f)
|
78
|
+
when SIGHASH_TYPE[:single]
|
79
|
+
hash_outputs = input_index >= tx.outputs.size ? "\x00".ljust(32, "\x00") : Bitcoin.double_sha256(tx.outputs[input_index].to_payload)
|
80
|
+
hash_sequence = "\x00".ljust(32, "\x00")
|
81
|
+
when SIGHASH_TYPE[:none]
|
82
|
+
hash_sequence = hash_outputs = "\x00".ljust(32, "\x00")
|
83
|
+
end
|
84
|
+
|
85
|
+
unless (hash_type & SIGHASH_TYPE[:anyonecanpay]) == 0
|
86
|
+
hash_prevouts = hash_sequence ="\x00".ljust(32, "\x00")
|
87
|
+
end
|
88
|
+
|
89
|
+
buf = [ [tx.version].pack('V'), hash_prevouts, hash_sequence, outpoint,
|
90
|
+
script_code ,amount, nsequence, hash_outputs, [tx.lock_time, hash_type].pack('VV')].join
|
91
|
+
Bitcoin.double_sha256(buf)
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
# v1 witness sighash generator
|
97
|
+
# see: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki
|
98
|
+
class SchnorrSigHashGenerator
|
99
|
+
|
100
|
+
# generate signature hash for taproot and tapscript
|
101
|
+
# @param [Hash] opts some data using signature. This class requires following key params:
|
102
|
+
# - sig_version: sig version. :taproot or :tapscript
|
103
|
+
# - prevouts: array of all prevout[Txout]
|
104
|
+
# - annex: annex value with binary format if annex exist.
|
105
|
+
# - leaf_hash: leaf hash with binary format if sig_version is :tapscript, it required
|
106
|
+
# - last_code_separator_pos: the position of last code separator
|
107
|
+
# @return [String] signature hash with binary format.
|
108
|
+
def generate(tx, input_index, hash_type, opts)
|
109
|
+
raise ArgumentError, 'Invalid sig_version was specified.' unless [:taproot, :tapscript].include?(opts[:sig_version])
|
110
|
+
|
111
|
+
ext_flag = opts[:sig_version] == :taproot ? 0 : 1
|
112
|
+
key_version = 0
|
113
|
+
output_ype = hash_type == SIGHASH_TYPE[:default] ? SIGHASH_TYPE[:all] : (hash_type & 0x03)
|
114
|
+
input_type = hash_type & 0x80
|
115
|
+
epoc = '00'.htb
|
116
|
+
|
117
|
+
buf = epoc # EPOC
|
118
|
+
buf << [hash_type, tx.version, tx.lock_time].pack('CVV')
|
119
|
+
unless input_type == SIGHASH_TYPE[:anyonecanpay]
|
120
|
+
buf << Bitcoin.sha256(tx.in.map{|i|i.out_point.to_payload}.join) # sha_prevouts
|
121
|
+
buf << Bitcoin.sha256(opts[:prevouts].map(&:value).pack('Q*'))# sha_amounts
|
122
|
+
buf << Bitcoin.sha256(opts[:prevouts].map{|o|o.script_pubkey.to_payload(true)}.join) # sha_scriptpubkeys
|
123
|
+
buf << Bitcoin.sha256(tx.in.map(&:sequence).pack('V*')) # sha_sequences
|
124
|
+
end
|
125
|
+
|
126
|
+
buf << Bitcoin.sha256(tx.out.map(&:to_payload).join) if output_ype == SIGHASH_TYPE[:all]
|
127
|
+
|
128
|
+
spend_type = (ext_flag << 1) + (opts[:annex] ? 1 : 0)
|
129
|
+
buf << [spend_type].pack('C')
|
130
|
+
if input_type == SIGHASH_TYPE[:anyonecanpay]
|
131
|
+
buf << tx.in[input_index].out_point.to_payload
|
132
|
+
buf << opts[:prevouts][input_index].to_payload
|
133
|
+
buf << [tx.in[input_index].sequence].pack('V')
|
134
|
+
else
|
135
|
+
buf << [input_index].pack('V')
|
136
|
+
end
|
137
|
+
|
138
|
+
buf << Bitcoin.sha256(Bitcoin.pack_var_string(opts[:annex])) if opts[:annex]
|
139
|
+
|
140
|
+
if output_ype == SIGHASH_TYPE[:single]
|
141
|
+
raise ArgumentError, "Tx does not have #{input_index} th output." if input_index >= tx.out.size
|
142
|
+
buf << Bitcoin.sha256(tx.out[input_index].to_payload)
|
143
|
+
end
|
144
|
+
|
145
|
+
if opts[:sig_version] == :tapscript
|
146
|
+
buf << opts[:leaf_hash]
|
147
|
+
buf << [key_version, opts[:last_code_separator_pos]].pack("CV")
|
148
|
+
end
|
149
|
+
|
150
|
+
Bitcoin.tagged_hash('TapSighash', buf)
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
@@ -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 = Bitcoin::TAPROOT_LEAF_TAPSCRIPT)
|
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
|