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.
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