bitcoinrb 0.3.2 → 0.8.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 (91) 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 +17 -6
  6. data/bitcoinrb.gemspec +9 -8
  7. data/exe/bitcoinrbd +5 -0
  8. data/lib/bitcoin.rb +37 -19
  9. data/lib/bitcoin/bip85_entropy.rb +111 -0
  10. data/lib/bitcoin/block_filter.rb +14 -0
  11. data/lib/bitcoin/block_header.rb +2 -0
  12. data/lib/bitcoin/chain_params.rb +9 -8
  13. data/lib/bitcoin/chainparams/regtest.yml +1 -1
  14. data/lib/bitcoin/chainparams/signet.yml +39 -0
  15. data/lib/bitcoin/chainparams/testnet.yml +1 -1
  16. data/lib/bitcoin/constants.rb +44 -10
  17. data/lib/bitcoin/descriptor.rb +1 -1
  18. data/lib/bitcoin/errors.rb +19 -0
  19. data/lib/bitcoin/ext.rb +6 -0
  20. data/lib/bitcoin/ext/array_ext.rb +22 -0
  21. data/lib/bitcoin/ext/ecdsa.rb +36 -0
  22. data/lib/bitcoin/ext/json_parser.rb +46 -0
  23. data/lib/bitcoin/ext_key.rb +51 -20
  24. data/lib/bitcoin/key.rb +89 -30
  25. data/lib/bitcoin/key_path.rb +12 -5
  26. data/lib/bitcoin/message.rb +79 -0
  27. data/lib/bitcoin/message/addr_v2.rb +34 -0
  28. data/lib/bitcoin/message/base.rb +17 -0
  29. data/lib/bitcoin/message/cf_parser.rb +16 -0
  30. data/lib/bitcoin/message/cfcheckpt.rb +36 -0
  31. data/lib/bitcoin/message/cfheaders.rb +40 -0
  32. data/lib/bitcoin/message/cfilter.rb +35 -0
  33. data/lib/bitcoin/message/fee_filter.rb +1 -1
  34. data/lib/bitcoin/message/filter_load.rb +3 -3
  35. data/lib/bitcoin/message/get_cfcheckpt.rb +29 -0
  36. data/lib/bitcoin/message/get_cfheaders.rb +24 -0
  37. data/lib/bitcoin/message/get_cfilters.rb +25 -0
  38. data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
  39. data/lib/bitcoin/message/inventory.rb +1 -1
  40. data/lib/bitcoin/message/merkle_block.rb +1 -1
  41. data/lib/bitcoin/message/network_addr.rb +141 -18
  42. data/lib/bitcoin/message/ping.rb +1 -1
  43. data/lib/bitcoin/message/pong.rb +1 -1
  44. data/lib/bitcoin/message/send_addr_v2.rb +13 -0
  45. data/lib/bitcoin/message/send_cmpct.rb +2 -2
  46. data/lib/bitcoin/message/tx.rb +1 -1
  47. data/lib/bitcoin/message/version.rb +7 -0
  48. data/lib/bitcoin/message_sign.rb +47 -0
  49. data/lib/bitcoin/mnemonic.rb +7 -7
  50. data/lib/bitcoin/network/peer.rb +9 -4
  51. data/lib/bitcoin/network/peer_discovery.rb +1 -1
  52. data/lib/bitcoin/node/cli.rb +14 -10
  53. data/lib/bitcoin/node/configuration.rb +3 -1
  54. data/lib/bitcoin/node/spv.rb +9 -1
  55. data/lib/bitcoin/opcodes.rb +14 -1
  56. data/lib/bitcoin/out_point.rb +2 -0
  57. data/lib/bitcoin/payment_code.rb +92 -0
  58. data/lib/bitcoin/payments/payment.pb.rb +1 -1
  59. data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
  60. data/lib/bitcoin/psbt/input.rb +9 -18
  61. data/lib/bitcoin/psbt/output.rb +1 -1
  62. data/lib/bitcoin/psbt/tx.rb +12 -17
  63. data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
  64. data/lib/bitcoin/rpc/request_handler.rb +5 -5
  65. data/lib/bitcoin/script/script.rb +96 -39
  66. data/lib/bitcoin/script/script_error.rb +27 -1
  67. data/lib/bitcoin/script/script_interpreter.rb +166 -66
  68. data/lib/bitcoin/script/tx_checker.rb +62 -14
  69. data/lib/bitcoin/secp256k1.rb +1 -0
  70. data/lib/bitcoin/secp256k1/native.rb +184 -17
  71. data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
  72. data/lib/bitcoin/secp256k1/ruby.rb +112 -56
  73. data/lib/bitcoin/sighash_generator.rb +156 -0
  74. data/lib/bitcoin/store.rb +1 -0
  75. data/lib/bitcoin/store/chain_entry.rb +1 -0
  76. data/lib/bitcoin/store/utxo_db.rb +226 -0
  77. data/lib/bitcoin/taproot.rb +9 -0
  78. data/lib/bitcoin/taproot/leaf_node.rb +23 -0
  79. data/lib/bitcoin/taproot/simple_builder.rb +139 -0
  80. data/lib/bitcoin/tx.rb +34 -104
  81. data/lib/bitcoin/tx_in.rb +4 -5
  82. data/lib/bitcoin/tx_out.rb +2 -3
  83. data/lib/bitcoin/util.rb +22 -6
  84. data/lib/bitcoin/version.rb +1 -1
  85. data/lib/bitcoin/wallet.rb +1 -0
  86. data/lib/bitcoin/wallet/account.rb +2 -1
  87. data/lib/bitcoin/wallet/base.rb +2 -2
  88. data/lib/bitcoin/wallet/master_key.rb +1 -0
  89. data/lib/bitcoin/wallet/utxo.rb +37 -0
  90. metadata +86 -32
  91. data/.travis.yml +0 -11
@@ -6,6 +6,7 @@ module Bitcoin
6
6
 
7
7
  autoload :Ruby, 'bitcoin/secp256k1/ruby'
8
8
  autoload :Native, 'bitcoin/secp256k1/native'
9
+ autoload :RFC6979, 'bitcoin/secp256k1/rfc6979'
9
10
 
10
11
  end
11
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
@@ -0,0 +1,43 @@
1
+ module Bitcoin
2
+ module Secp256k1
3
+ module RFC6979
4
+
5
+ INITIAL_V = '0101010101010101010101010101010101010101010101010101010101010101'.htb
6
+ INITIAL_K = '0000000000000000000000000000000000000000000000000000000000000000'.htb
7
+ ZERO_B = '00'.htb
8
+ ONE_B = '01'.htb
9
+
10
+ module_function
11
+
12
+ # generate temporary key k to be used when ECDSA sign.
13
+ # https://tools.ietf.org/html/rfc6979#section-3.2
14
+ # @param [String] key_data a data contains private key and message.
15
+ # @param [String] extra_entropy extra entropy with binary format.
16
+ # @return [Integer] a nonce.
17
+ def generate_rfc6979_nonce(key_data, extra_entropy)
18
+ v = INITIAL_V # 3.2.b
19
+ k = INITIAL_K # 3.2.c
20
+ # 3.2.d
21
+ k = Bitcoin.hmac_sha256(k, v + ZERO_B + key_data + extra_entropy)
22
+ # 3.2.e
23
+ v = Bitcoin.hmac_sha256(k, v)
24
+ # 3.2.f
25
+ k = Bitcoin.hmac_sha256(k, v + ONE_B + key_data + extra_entropy)
26
+ # 3.2.g
27
+ v = Bitcoin.hmac_sha256(k, v)
28
+ # 3.2.h
29
+ t = ''
30
+ 10000.times do
31
+ v = Bitcoin.hmac_sha256(k, v)
32
+ t = (t + v)
33
+ t_num = t.bth.to_i(16)
34
+ return t_num if 1 <= t_num && t_num < Bitcoin::Secp256k1::GROUP.order
35
+ k = Bitcoin.hmac_sha256(k, v + '00'.htb)
36
+ v = Bitcoin.hmac_sha256(k, v)
37
+ end
38
+ raise 'A valid nonce was not found.'
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -12,8 +12,8 @@ module Bitcoin
12
12
  private_key = 1 + SecureRandom.random_number(GROUP.order - 1)
13
13
  public_key = GROUP.generator.multiply_by_scalar(private_key)
14
14
  privkey = ECDSA::Format::IntegerOctetString.encode(private_key, 32)
15
- pubkey = ECDSA::Format::PointOctetString.encode(public_key, compression: compressed)
16
- [privkey.bth, pubkey.bth]
15
+ pubkey = public_key.to_hex(compressed)
16
+ [privkey.bth, pubkey]
17
17
  end
18
18
 
19
19
  # generate bitcoin key object
@@ -24,18 +24,112 @@ module Bitcoin
24
24
 
25
25
  def generate_pubkey(privkey, compressed: true)
26
26
  public_key = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(privkey.to_i(16))
27
- ECDSA::Format::PointOctetString.encode(public_key, compression: compressed).bth
27
+ public_key.to_hex(compressed)
28
+ end
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
28
42
  end
29
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 ||= ''
38
- nonce = generate_rfc6979_nonce(data, privkey, extra_entropy)
132
+ nonce = RFC6979.generate_rfc6979_nonce(privkey + data, extra_entropy)
39
133
 
40
134
  # port form ecdsa gem.
41
135
  r_point = GROUP.new_point(nonce)
@@ -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,68 +153,27 @@ 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
- INITIAL_V = '0101010101010101010101010101010101010101010101010101010101010101'.htb
91
- INITIAL_K = '0000000000000000000000000000000000000000000000000000000000000000'.htb
92
- ZERO_B = '00'.htb
93
- ONE_B = '01'.htb
94
-
95
- # generate temporary key k to be used when ECDSA sign.
96
- # https://tools.ietf.org/html/rfc6979#section-3.2
97
- def generate_rfc6979_nonce(data, privkey, extra_entropy)
98
- v = INITIAL_V # 3.2.b
99
- k = INITIAL_K # 3.2.c
100
- # 3.2.d
101
- k = Bitcoin.hmac_sha256(k, v + ZERO_B + privkey + data + extra_entropy)
102
- # 3.2.e
103
- v = Bitcoin.hmac_sha256(k, v)
104
- # 3.2.f
105
- k = Bitcoin.hmac_sha256(k, v + ONE_B + privkey + data + extra_entropy)
106
- # 3.2.g
107
- v = Bitcoin.hmac_sha256(k, v)
108
- # 3.2.h
109
- t = ''
110
- 10000.times do
111
- v = Bitcoin.hmac_sha256(k, v)
112
- t = (t + v)
113
- t_num = t.bth.to_i(16)
114
- return t_num if 1 <= t_num && t_num < GROUP.order
115
- k = Bitcoin.hmac_sha256(k, v + '00'.htb)
116
- v = Bitcoin.hmac_sha256(k, v)
117
- end
118
- raise 'A valid nonce was not found.'
119
- end
120
177
  end
121
-
122
178
  end
123
179
  end