bitcoinrb 0.3.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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