bitcoinrb 0.5.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +37 -0
  3. data/.rspec_parallel +2 -0
  4. data/.ruby-version +1 -1
  5. data/README.md +11 -1
  6. data/bitcoinrb.gemspec +7 -6
  7. data/lib/bitcoin/block_filter.rb +14 -0
  8. data/lib/bitcoin/chain_params.rb +9 -0
  9. data/lib/bitcoin/chainparams/signet.yml +39 -0
  10. data/lib/bitcoin/constants.rb +45 -4
  11. data/lib/bitcoin/descriptor.rb +1 -1
  12. data/lib/bitcoin/errors.rb +19 -0
  13. data/lib/bitcoin/ext/array_ext.rb +22 -0
  14. data/lib/bitcoin/ext/ecdsa.rb +36 -0
  15. data/lib/bitcoin/ext.rb +1 -0
  16. data/lib/bitcoin/ext_key.rb +36 -20
  17. data/lib/bitcoin/key.rb +85 -28
  18. data/lib/bitcoin/message/addr_v2.rb +34 -0
  19. data/lib/bitcoin/message/base.rb +16 -0
  20. data/lib/bitcoin/message/cfcheckpt.rb +2 -2
  21. data/lib/bitcoin/message/cfheaders.rb +1 -1
  22. data/lib/bitcoin/message/cfilter.rb +1 -1
  23. data/lib/bitcoin/message/fee_filter.rb +1 -1
  24. data/lib/bitcoin/message/filter_load.rb +3 -3
  25. data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
  26. data/lib/bitcoin/message/inventory.rb +1 -1
  27. data/lib/bitcoin/message/merkle_block.rb +1 -1
  28. data/lib/bitcoin/message/network_addr.rb +141 -18
  29. data/lib/bitcoin/message/ping.rb +1 -1
  30. data/lib/bitcoin/message/pong.rb +1 -1
  31. data/lib/bitcoin/message/send_addr_v2.rb +13 -0
  32. data/lib/bitcoin/message/send_cmpct.rb +2 -2
  33. data/lib/bitcoin/message/tx.rb +1 -1
  34. data/lib/bitcoin/message.rb +72 -0
  35. data/lib/bitcoin/message_sign.rb +47 -0
  36. data/lib/bitcoin/mnemonic.rb +2 -2
  37. data/lib/bitcoin/network/peer_discovery.rb +1 -3
  38. data/lib/bitcoin/node/configuration.rb +3 -1
  39. data/lib/bitcoin/node/spv.rb +8 -0
  40. data/lib/bitcoin/opcodes.rb +14 -1
  41. data/lib/bitcoin/payment_code.rb +2 -2
  42. data/lib/bitcoin/payments/payment.pb.rb +1 -1
  43. data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
  44. data/lib/bitcoin/psbt/input.rb +4 -4
  45. data/lib/bitcoin/psbt/output.rb +1 -1
  46. data/lib/bitcoin/psbt/tx.rb +14 -5
  47. data/lib/bitcoin/psbt.rb +8 -0
  48. data/lib/bitcoin/rpc/bitcoin_core_client.rb +1 -1
  49. data/lib/bitcoin/rpc/request_handler.rb +3 -3
  50. data/lib/bitcoin/script/script.rb +80 -30
  51. data/lib/bitcoin/script/script_error.rb +27 -1
  52. data/lib/bitcoin/script/script_interpreter.rb +164 -62
  53. data/lib/bitcoin/script/tx_checker.rb +62 -14
  54. data/lib/bitcoin/secp256k1/native.rb +184 -17
  55. data/lib/bitcoin/secp256k1/ruby.rb +108 -21
  56. data/lib/bitcoin/sighash_generator.rb +157 -0
  57. data/lib/bitcoin/taproot/leaf_node.rb +23 -0
  58. data/lib/bitcoin/taproot/simple_builder.rb +155 -0
  59. data/lib/bitcoin/taproot.rb +45 -0
  60. data/lib/bitcoin/tx.rb +30 -96
  61. data/lib/bitcoin/tx_in.rb +1 -1
  62. data/lib/bitcoin/tx_out.rb +2 -3
  63. data/lib/bitcoin/util.rb +15 -6
  64. data/lib/bitcoin/version.rb +1 -1
  65. data/lib/bitcoin/wallet/account.rb +1 -1
  66. data/lib/bitcoin.rb +32 -24
  67. metadata +58 -18
  68. 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/bitcoin/tree/v0.14.2/src/secp256k1)
8
- # tag: v0.14.2
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 verify_sig(data, sig, pub_key)
276
+ def sign_schnorr(data, privkey, aux_rand = nil)
130
277
  with_context do |context|
131
- return false if data.bytesize == 0
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
- pubkey = FFI::MemoryPointer.new(:uchar, pub_key.htb.bytesize).put_bytes(0, pub_key.htb)
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
- private
154
-
155
- def generate_pubkey_in_context(context, privkey, compressed: true)
156
- internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
157
- result = secp256k1_ec_pubkey_create(context, internal_pubkey, privkey.htb)
158
- raise 'error creating pubkey' unless result
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, internal_pubkey, SECP256K1_EC_COMPRESSED)
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, internal_pubkey, SECP256K1_EC_UNCOMPRESSED)
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
- # verify signature using public key
63
- # @param [String] digest a SHA-256 message digest with binary format
64
- # @param [String] sig a signature for +data+ with binary format
65
- # @param [String] pubkey a public key corresponding to the private key used for sign
66
- # @return [Boolean] verify result
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, digest, signature)
167
+ ECDSA.valid_signature?(k, data, signature)
72
168
  rescue Exception
73
169
  false
74
170
  end
75
171
  end
76
172
 
77
- # if +pubkey+ is hybrid public key format, it convert uncompressed format.
78
- # https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2012-June/001578.html
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