bitcoinrb 0.3.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +5 -3
  4. data/README.md +17 -6
  5. data/bitcoinrb.gemspec +7 -7
  6. data/exe/bitcoinrbd +5 -0
  7. data/lib/bitcoin.rb +34 -11
  8. data/lib/bitcoin/bip85_entropy.rb +111 -0
  9. data/lib/bitcoin/block_filter.rb +14 -0
  10. data/lib/bitcoin/block_header.rb +2 -0
  11. data/lib/bitcoin/chain_params.rb +9 -8
  12. data/lib/bitcoin/chainparams/regtest.yml +1 -1
  13. data/lib/bitcoin/chainparams/signet.yml +39 -0
  14. data/lib/bitcoin/chainparams/testnet.yml +1 -1
  15. data/lib/bitcoin/constants.rb +45 -12
  16. data/lib/bitcoin/descriptor.rb +1 -1
  17. data/lib/bitcoin/errors.rb +19 -0
  18. data/lib/bitcoin/ext.rb +5 -0
  19. data/lib/bitcoin/ext/ecdsa.rb +31 -0
  20. data/lib/bitcoin/ext/json_parser.rb +46 -0
  21. data/lib/bitcoin/ext_key.rb +50 -19
  22. data/lib/bitcoin/key.rb +46 -29
  23. data/lib/bitcoin/key_path.rb +12 -5
  24. data/lib/bitcoin/message.rb +7 -0
  25. data/lib/bitcoin/message/base.rb +1 -0
  26. data/lib/bitcoin/message/cf_parser.rb +16 -0
  27. data/lib/bitcoin/message/cfcheckpt.rb +36 -0
  28. data/lib/bitcoin/message/cfheaders.rb +40 -0
  29. data/lib/bitcoin/message/cfilter.rb +35 -0
  30. data/lib/bitcoin/message/fee_filter.rb +1 -1
  31. data/lib/bitcoin/message/filter_load.rb +3 -3
  32. data/lib/bitcoin/message/get_cfcheckpt.rb +29 -0
  33. data/lib/bitcoin/message/get_cfheaders.rb +24 -0
  34. data/lib/bitcoin/message/get_cfilters.rb +25 -0
  35. data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
  36. data/lib/bitcoin/message/inventory.rb +1 -1
  37. data/lib/bitcoin/message/merkle_block.rb +1 -1
  38. data/lib/bitcoin/message/network_addr.rb +3 -3
  39. data/lib/bitcoin/message/ping.rb +1 -1
  40. data/lib/bitcoin/message/pong.rb +1 -1
  41. data/lib/bitcoin/message/send_cmpct.rb +2 -2
  42. data/lib/bitcoin/message/version.rb +7 -0
  43. data/lib/bitcoin/mnemonic.rb +7 -7
  44. data/lib/bitcoin/network/peer.rb +9 -4
  45. data/lib/bitcoin/network/peer_discovery.rb +1 -1
  46. data/lib/bitcoin/node/cli.rb +14 -10
  47. data/lib/bitcoin/node/configuration.rb +3 -1
  48. data/lib/bitcoin/node/spv.rb +9 -1
  49. data/lib/bitcoin/opcodes.rb +14 -1
  50. data/lib/bitcoin/out_point.rb +7 -0
  51. data/lib/bitcoin/payment_code.rb +92 -0
  52. data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
  53. data/lib/bitcoin/psbt/input.rb +8 -17
  54. data/lib/bitcoin/psbt/output.rb +1 -1
  55. data/lib/bitcoin/psbt/tx.rb +11 -16
  56. data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
  57. data/lib/bitcoin/rpc/request_handler.rb +2 -2
  58. data/lib/bitcoin/script/script.rb +68 -28
  59. data/lib/bitcoin/script/script_error.rb +27 -1
  60. data/lib/bitcoin/script/script_interpreter.rb +164 -67
  61. data/lib/bitcoin/script/tx_checker.rb +64 -14
  62. data/lib/bitcoin/secp256k1.rb +1 -0
  63. data/lib/bitcoin/secp256k1/native.rb +138 -25
  64. data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
  65. data/lib/bitcoin/secp256k1/ruby.rb +82 -54
  66. data/lib/bitcoin/sighash_generator.rb +156 -0
  67. data/lib/bitcoin/slip39/sss.rb +5 -2
  68. data/lib/bitcoin/store.rb +2 -1
  69. data/lib/bitcoin/store/chain_entry.rb +1 -0
  70. data/lib/bitcoin/store/db/level_db.rb +2 -2
  71. data/lib/bitcoin/store/utxo_db.rb +226 -0
  72. data/lib/bitcoin/tx.rb +17 -88
  73. data/lib/bitcoin/tx_in.rb +4 -5
  74. data/lib/bitcoin/tx_out.rb +2 -3
  75. data/lib/bitcoin/util.rb +43 -6
  76. data/lib/bitcoin/version.rb +1 -1
  77. data/lib/bitcoin/wallet.rb +1 -0
  78. data/lib/bitcoin/wallet/account.rb +2 -1
  79. data/lib/bitcoin/wallet/base.rb +3 -3
  80. data/lib/bitcoin/wallet/db.rb +1 -1
  81. data/lib/bitcoin/wallet/master_key.rb +1 -0
  82. data/lib/bitcoin/wallet/utxo.rb +37 -0
  83. metadata +50 -32
@@ -4,28 +4,67 @@ module Bitcoin
4
4
  attr_reader :tx
5
5
  attr_reader :input_index
6
6
  attr_reader :amount
7
+ attr_reader :prevouts
8
+ attr_accessor :error_code
7
9
 
8
- def initialize(tx: nil, amount: 0, input_index: nil)
10
+ def initialize(tx: nil, amount: 0, input_index: nil, prevouts: [])
9
11
  @tx = tx
10
- @amount = amount
11
12
  @input_index = input_index
13
+ @prevouts = prevouts
14
+ @amount = input_index && prevouts[input_index] ? prevouts[input_index].value : amount
12
15
  end
13
16
 
14
- # check signature
15
- # @param [String] script_sig
16
- # @param [String] pubkey
17
+ # check ecdsa signature
18
+ # @param [String] sig signature with hex format
19
+ # @param [String] pubkey with hex format.
17
20
  # @param [Bitcoin::Script] script_code
18
21
  # @param [Integer] sig_version
19
- def check_sig(script_sig, pubkey, script_code, sig_version)
20
- return false if script_sig.empty?
21
- script_sig = script_sig.htb
22
- hash_type = script_sig[-1].unpack('C').first
23
- sig = script_sig[0..-2]
24
- sighash = tx.sighash_for_input(input_index, script_code, hash_type: hash_type,
25
- amount: amount, sig_version: sig_version)
22
+ # @return [Boolean] verification result
23
+ def check_sig(sig, pubkey, script_code, sig_version, allow_hybrid: false)
24
+ return false if sig.empty?
25
+ sig = sig.htb
26
+ hash_type = sig[-1].unpack1('C')
27
+ sig = sig[0..-2]
28
+ sighash = tx.sighash_for_input(input_index, script_code, opts: {amount: amount}, hash_type: hash_type, sig_version: sig_version)
26
29
  key_type = pubkey.start_with?('02') || pubkey.start_with?('03') ? Key::TYPES[:compressed] : Key::TYPES[:uncompressed]
27
- key = Key.new(pubkey: pubkey, key_type: key_type)
28
- key.verify(sig, sighash)
30
+ begin
31
+ key = Key.new(pubkey: pubkey, key_type: key_type, allow_hybrid: allow_hybrid)
32
+ key.verify(sig, sighash)
33
+ rescue Exception
34
+ false
35
+ end
36
+ end
37
+
38
+ # check schnorr signature.
39
+ # @param [String] sig schnorr signature with hex format.
40
+ # @param [String] pubkey a public key with hex fromat.
41
+ # @param [Symbol] sig_version whether :taproot or :tapscript
42
+ # @return [Boolean] verification result
43
+ def check_schnorr_sig(sig, pubkey, sig_version, opts = {})
44
+ return false unless [:taproot, :tapscript].include?(sig_version)
45
+ return false if prevouts.size < input_index
46
+
47
+ sig = sig.htb
48
+ return set_error(SCRIPT_ERR_SCHNORR_SIG_SIZE) unless [64, 65].include?(sig.bytesize)
49
+
50
+ hash_type = SIGHASH_TYPE[:default]
51
+ if sig.bytesize == 65
52
+ hash_type = sig[-1].unpack1('C')
53
+ sig = sig[0..-2]
54
+ return set_error(SCRIPT_ERR_SCHNORR_SIG_HASHTYPE) if hash_type == SIGHASH_TYPE[:default] # hash type can not specify 0x00.
55
+ end
56
+
57
+ return set_error(SCRIPT_ERR_SCHNORR_SIG_HASHTYPE) unless (hash_type <= 0x03 || (hash_type >= 0x81 && hash_type <= 0x83))
58
+
59
+ opts[:prevouts] = prevouts
60
+
61
+ begin
62
+ sighash = tx.sighash_for_input(input_index, opts: opts, hash_type: hash_type, sig_version: sig_version)
63
+ key = Key.new(pubkey: "02#{pubkey}", key_type: Key::TYPES[:compressed])
64
+ key.verify(sig, sighash, algo: :schnorr)
65
+ rescue ArgumentError
66
+ return set_error(SCRIPT_ERR_SCHNORR_SIG_HASHTYPE)
67
+ end
29
68
  end
30
69
 
31
70
  def check_locktime(locktime)
@@ -77,5 +116,16 @@ module Bitcoin
77
116
  sequence_masked <= tx_sequence_masked
78
117
  end
79
118
 
119
+ def has_error?
120
+ !@error_code.nil?
121
+ end
122
+
123
+ private
124
+
125
+ def set_error(code)
126
+ @error_code = code
127
+ false
128
+ end
129
+
80
130
  end
81
131
  end
@@ -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
 
@@ -50,6 +50,10 @@ 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)
53
57
  end
54
58
 
55
59
  def with_context(flags: (SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN))
@@ -97,13 +101,117 @@ module Bitcoin
97
101
 
98
102
  # sign data.
99
103
  # @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
104
+ # @param [String] privkey a private key with hex format using sign
105
+ # @param [String] extra_entropy a extra entropy with binary format for rfc6979
106
+ # @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
102
107
  # @return [String] signature data with binary format
103
- def sign_data(data, privkey, extra_entropy)
108
+ def sign_data(data, privkey, extra_entropy = nil, algo: :ecdsa)
109
+ case algo
110
+ when :ecdsa
111
+ sign_ecdsa(data, privkey, extra_entropy)
112
+ when :schnorr
113
+ sign_schnorr(data, privkey, extra_entropy)
114
+ else
115
+ nil
116
+ end
117
+ end
118
+
119
+ # verify signature
120
+ # @param [String] data a data with binary format.
121
+ # @param [String] sig signature data with binary format
122
+ # @param [String] pubkey a public key with hex format using verify.
123
+ # # @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
124
+ # @return [Boolean] verification result.
125
+ def verify_sig(data, sig, pubkey, algo: :ecdsa)
126
+ case algo
127
+ when :ecdsa
128
+ verify_ecdsa(data, sig, pubkey)
129
+ when :schnorr
130
+ verify_schnorr(data, sig, pubkey)
131
+ else
132
+ false
133
+ end
134
+ end
135
+
136
+ # # validate whether this is a valid public key (more expensive than IsValid())
137
+ # @param [String] pub_key public key with hex format.
138
+ # @param [Boolean] allow_hybrid whether support hybrid public key.
139
+ # @return [Boolean] If valid public key return true, otherwise false.
140
+ def parse_ec_pubkey?(pub_key, allow_hybrid = false)
141
+ pub_key = pub_key.htb
142
+ return false if !allow_hybrid && ![0x02, 0x03, 0x04].include?(pub_key[0].ord)
143
+ with_context do |context|
144
+ pubkey = FFI::MemoryPointer.new(:uchar, pub_key.bytesize).put_bytes(0, pub_key)
145
+ internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
146
+ result = secp256k1_ec_pubkey_parse(context, internal_pubkey, pubkey, pub_key.bytesize)
147
+ result == 1
148
+ end
149
+ end
150
+
151
+ # Create key pair data from private key.
152
+ # @param [String] priv_key with hex format
153
+ # @return [String] key pair data with hex format. data = private key(32 bytes) | public key(64 bytes).
154
+ def create_keypair(priv_key)
155
+ with_context do |context|
156
+ priv_key = priv_key.htb
157
+ secret = FFI::MemoryPointer.new(:uchar, priv_key.bytesize).put_bytes(0, priv_key)
158
+ raise 'priv_key is invalid.' unless secp256k1_ec_seckey_verify(context, secret)
159
+ keypair = FFI::MemoryPointer.new(:uchar, 96)
160
+ raise 'priv_key is invalid.' unless secp256k1_keypair_create(context, keypair, secret) == 1
161
+ keypair.read_string(96).bth
162
+ end
163
+ end
164
+
165
+ # Check whether valid x-only public key or not.
166
+ # @param [String] pub_key x-only public key with hex format(32 bytes).
167
+ # @return [Boolean] result.
168
+ def valid_xonly_pubkey?(pub_key)
169
+ begin
170
+ full_pubkey_from_xonly_pubkey(pub_key)
171
+ rescue Exception
172
+ return false
173
+ end
174
+ true
175
+ end
176
+
177
+ private
178
+
179
+ # Calculate full public key(64 bytes) from public key(32 bytes).
180
+ # @param [String] pub_key x-only public key with hex format(32 bytes).
181
+ # @return [String] x-only public key with hex format(64 bytes).
182
+ def full_pubkey_from_xonly_pubkey(pub_key)
183
+ with_context do |context|
184
+ pubkey = pub_key.htb
185
+ raise ArgumentError, 'Pubkey size must be 32 bytes.' unless pubkey.bytesize == 32
186
+ xonly_pubkey = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
187
+ full_pubkey = FFI::MemoryPointer.new(:uchar, 64)
188
+ raise ArgumentError, 'An invalid public key was specified.' unless secp256k1_xonly_pubkey_parse(context, full_pubkey, xonly_pubkey) == 1
189
+ full_pubkey.read_string(64).bth
190
+ end
191
+ end
192
+
193
+ def generate_pubkey_in_context(context, privkey, compressed: true)
194
+ internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
195
+ result = secp256k1_ec_pubkey_create(context, internal_pubkey, privkey.htb)
196
+ raise 'error creating pubkey' unless result
197
+
198
+ pubkey = FFI::MemoryPointer.new(:uchar, 65)
199
+ pubkey_len = FFI::MemoryPointer.new(:uint64)
200
+ result = if compressed
201
+ pubkey_len.put_uint64(0, 33)
202
+ secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_COMPRESSED)
203
+ else
204
+ pubkey_len.put_uint64(0, 65)
205
+ secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_UNCOMPRESSED)
206
+ end
207
+ raise 'error serialize pubkey' unless result || pubkey_len.read_uint64 > 0
208
+ pubkey.read_string(pubkey_len.read_uint64).bth
209
+ end
210
+
211
+ def sign_ecdsa(data, privkey, extra_entropy)
104
212
  with_context do |context|
105
213
  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)
214
+ raise 'priv_key is invalid' unless secp256k1_ec_seckey_verify(context, secret)
107
215
 
108
216
  internal_signature = FFI::MemoryPointer.new(:uchar, 64)
109
217
  msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
@@ -126,11 +234,23 @@ module Bitcoin
126
234
  end
127
235
  end
128
236
 
129
- def verify_sig(data, sig, pub_key)
237
+ def sign_schnorr(data, privkey, aux_rand = nil)
130
238
  with_context do |context|
131
- return false if data.bytesize == 0
239
+ keypair = create_keypair(privkey).htb
240
+ keypair = FFI::MemoryPointer.new(:uchar, 96).put_bytes(0, keypair)
241
+ signature = FFI::MemoryPointer.new(:uchar, 64)
242
+ msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
243
+ aux_rand = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, aux_rand) if aux_rand
244
+ raise 'Failed to generate schnorr signature.' unless secp256k1_schnorrsig_sign(context, signature, msg32, keypair, nil, aux_rand) == 1
245
+ signature.read_string(64)
246
+ end
247
+ end
132
248
 
133
- pubkey = FFI::MemoryPointer.new(:uchar, pub_key.htb.bytesize).put_bytes(0, pub_key.htb)
249
+ def verify_ecdsa(data, sig, pubkey)
250
+ with_context do |context|
251
+ return false if data.bytesize == 0
252
+ pubkey = pubkey.htb
253
+ pubkey = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
134
254
  internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
135
255
  result = secp256k1_ec_pubkey_parse(context, internal_pubkey, pubkey, pubkey.size)
136
256
  return false unless result
@@ -150,25 +270,18 @@ module Bitcoin
150
270
  end
151
271
  end
152
272
 
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
159
-
160
- pubkey = FFI::MemoryPointer.new(:uchar, 65)
161
- pubkey_len = FFI::MemoryPointer.new(:uint64)
162
- result = if compressed
163
- pubkey_len.put_uint64(0, 33)
164
- secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_COMPRESSED)
165
- else
166
- pubkey_len.put_uint64(0, 65)
167
- secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_UNCOMPRESSED)
168
- end
169
- raise 'error serialize pubkey' unless result || pubkey_len.read_uint64 > 0
170
- pubkey.read_string(pubkey_len.read_uint64).bth
273
+ def verify_schnorr(data, sig, pubkey)
274
+ with_context do |context|
275
+ return false if data.bytesize == 0
276
+ pubkey = full_pubkey_from_xonly_pubkey(pubkey).htb
277
+ xonly_pubkey = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
278
+ signature = FFI::MemoryPointer.new(:uchar, sig.bytesize).put_bytes(0, sig)
279
+ msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
280
+ result = secp256k1_schnorrsig_verify(context, signature, msg32, xonly_pubkey)
281
+ result == 1
282
+ end
171
283
  end
284
+
172
285
  end
173
286
  end
174
287
  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,87 @@ 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
46
  # @param [String] privkey a private key using sign
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)
54
+ when :schnorr
55
+ sign_schnorr(data, privkey, extra_entropy)
56
+ else
57
+ nil
58
+ end
59
+ end
60
+
61
+ # verify signature using public key
62
+ # @param [String] data a SHA-256 message digest with binary format
63
+ # @param [String] sig a signature for +data+ with binary format
64
+ # @param [String] pubkey a public key with hex format.
65
+ # @return [Boolean] verify result
66
+ def verify_sig(data, sig, pubkey, algo: :ecdsa)
67
+ case algo
68
+ when :ecdsa
69
+ verify_ecdsa(data, sig, pubkey)
70
+ when :schnorr
71
+ verify_schnorr(data, sig, pubkey)
72
+ else
73
+ false
74
+ end
75
+ end
76
+
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
88
+ end
89
+
90
+ # validate whether this is a valid public key (more expensive than IsValid())
91
+ # @param [String] pubkey public key with hex format.
92
+ # @param [Boolean] allow_hybrid whether support hybrid public key.
93
+ # @return [Boolean] If valid public key return true, otherwise false.
94
+ def parse_ec_pubkey?(pubkey, allow_hybrid = false)
95
+ begin
96
+ point = ECDSA::Format::PointOctetString.decode(pubkey.htb, ECDSA::Group::Secp256k1, allow_hybrid: allow_hybrid)
97
+ ECDSA::Group::Secp256k1.valid_public_key?(point)
98
+ rescue ECDSA::Format::DecodeError
99
+ false
100
+ end
101
+ end
102
+
103
+ def sign_ecdsa(data, privkey, extra_entropy)
35
104
  privkey = privkey.htb
36
105
  private_key = ECDSA::Format::IntegerOctetString.decode(privkey)
37
106
  extra_entropy ||= ''
38
- nonce = generate_rfc6979_nonce(data, privkey, extra_entropy)
107
+ nonce = RFC6979.generate_rfc6979_nonce(privkey + data, extra_entropy)
39
108
 
40
109
  # port form ecdsa gem.
41
110
  r_point = GROUP.new_point(nonce)
@@ -59,65 +128,24 @@ module Bitcoin
59
128
  signature
60
129
  end
61
130
 
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)
131
+ def sign_schnorr(data, privkey, aux_rand)
132
+ aux_rand ? Schnorr.sign(data, privkey.htb, aux_rand).encode : Schnorr.sign(data, privkey.htb).encode
133
+ end
134
+
135
+ def verify_ecdsa(data, sig, pubkey)
68
136
  begin
69
137
  k = ECDSA::Format::PointOctetString.decode(repack_pubkey(pubkey), GROUP)
70
138
  signature = ECDSA::Format::SignatureDerString.decode(sig)
71
- ECDSA.valid_signature?(k, digest, signature)
139
+ ECDSA.valid_signature?(k, data, signature)
72
140
  rescue Exception
73
141
  false
74
142
  end
75
143
  end
76
144
 
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
145
+ def verify_schnorr(data, sig, pubkey)
146
+ Schnorr.valid_sig?(data, pubkey.htb, sig)
88
147
  end
89
148
 
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
149
  end
121
-
122
150
  end
123
151
  end