bitcoinrb 0.3.0 → 0.6.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 (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