bitcoinrb 0.3.1 → 0.7.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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +6 -3
  4. data/README.md +17 -6
  5. data/bitcoinrb.gemspec +9 -8
  6. data/exe/bitcoinrbd +5 -0
  7. data/lib/bitcoin.rb +35 -19
  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 +79 -0
  25. data/lib/bitcoin/message/addr_v2.rb +34 -0
  26. data/lib/bitcoin/message/base.rb +17 -0
  27. data/lib/bitcoin/message/cf_parser.rb +16 -0
  28. data/lib/bitcoin/message/cfcheckpt.rb +36 -0
  29. data/lib/bitcoin/message/cfheaders.rb +40 -0
  30. data/lib/bitcoin/message/cfilter.rb +35 -0
  31. data/lib/bitcoin/message/fee_filter.rb +1 -1
  32. data/lib/bitcoin/message/filter_load.rb +3 -3
  33. data/lib/bitcoin/message/get_cfcheckpt.rb +29 -0
  34. data/lib/bitcoin/message/get_cfheaders.rb +24 -0
  35. data/lib/bitcoin/message/get_cfilters.rb +25 -0
  36. data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
  37. data/lib/bitcoin/message/inventory.rb +1 -1
  38. data/lib/bitcoin/message/merkle_block.rb +1 -1
  39. data/lib/bitcoin/message/network_addr.rb +141 -18
  40. data/lib/bitcoin/message/ping.rb +1 -1
  41. data/lib/bitcoin/message/pong.rb +1 -1
  42. data/lib/bitcoin/message/send_addr_v2.rb +13 -0
  43. data/lib/bitcoin/message/send_cmpct.rb +2 -2
  44. data/lib/bitcoin/message/version.rb +7 -0
  45. data/lib/bitcoin/mnemonic.rb +7 -7
  46. data/lib/bitcoin/network/peer.rb +9 -4
  47. data/lib/bitcoin/network/peer_discovery.rb +1 -1
  48. data/lib/bitcoin/node/cli.rb +14 -10
  49. data/lib/bitcoin/node/configuration.rb +3 -1
  50. data/lib/bitcoin/node/spv.rb +9 -1
  51. data/lib/bitcoin/opcodes.rb +14 -1
  52. data/lib/bitcoin/out_point.rb +7 -0
  53. data/lib/bitcoin/payment_code.rb +92 -0
  54. data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
  55. data/lib/bitcoin/psbt/input.rb +8 -17
  56. data/lib/bitcoin/psbt/output.rb +1 -1
  57. data/lib/bitcoin/psbt/tx.rb +11 -16
  58. data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
  59. data/lib/bitcoin/rpc/request_handler.rb +3 -3
  60. data/lib/bitcoin/script/script.rb +68 -28
  61. data/lib/bitcoin/script/script_error.rb +27 -1
  62. data/lib/bitcoin/script/script_interpreter.rb +164 -67
  63. data/lib/bitcoin/script/tx_checker.rb +64 -14
  64. data/lib/bitcoin/secp256k1.rb +1 -0
  65. data/lib/bitcoin/secp256k1/native.rb +138 -25
  66. data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
  67. data/lib/bitcoin/secp256k1/ruby.rb +82 -54
  68. data/lib/bitcoin/sighash_generator.rb +156 -0
  69. data/lib/bitcoin/store.rb +2 -1
  70. data/lib/bitcoin/store/chain_entry.rb +1 -0
  71. data/lib/bitcoin/store/db/level_db.rb +2 -2
  72. data/lib/bitcoin/store/utxo_db.rb +226 -0
  73. data/lib/bitcoin/tx.rb +17 -88
  74. data/lib/bitcoin/tx_in.rb +4 -5
  75. data/lib/bitcoin/tx_out.rb +2 -3
  76. data/lib/bitcoin/util.rb +34 -6
  77. data/lib/bitcoin/version.rb +1 -1
  78. data/lib/bitcoin/wallet.rb +1 -0
  79. data/lib/bitcoin/wallet/account.rb +2 -1
  80. data/lib/bitcoin/wallet/base.rb +3 -3
  81. data/lib/bitcoin/wallet/db.rb +1 -1
  82. data/lib/bitcoin/wallet/master_key.rb +1 -0
  83. data/lib/bitcoin/wallet/utxo.rb +37 -0
  84. metadata +66 -32
@@ -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
@@ -0,0 +1,156 @@
1
+ module Bitcoin
2
+
3
+ module SigHashGenerator
4
+
5
+ def self.load(sig_ver)
6
+ case sig_ver
7
+ when :base
8
+ LegacySigHashGenerator.new
9
+ when :witness_v0
10
+ SegwitSigHashGenerator.new
11
+ when :taproot, :tapscript
12
+ SchnorrSigHashGenerator.new
13
+ else
14
+ raise ArgumentError, "Unsupported sig version specified. #{sig_ver}"
15
+ end
16
+ end
17
+
18
+ # Legacy SigHash Generator
19
+ class LegacySigHashGenerator
20
+
21
+ def generate(tx, input_index, hash_type, opts)
22
+ output_script = opts[:script_code]
23
+ ins = tx.inputs.map.with_index do |i, idx|
24
+ if idx == input_index
25
+ i.to_payload(output_script.delete_opcode(Bitcoin::Opcodes::OP_CODESEPARATOR))
26
+ else
27
+ case hash_type & 0x1f
28
+ when SIGHASH_TYPE[:none], SIGHASH_TYPE[:single]
29
+ i.to_payload(Bitcoin::Script.new, 0)
30
+ else
31
+ i.to_payload(Bitcoin::Script.new)
32
+ end
33
+ end
34
+ end
35
+
36
+ outs = tx.outputs.map(&:to_payload)
37
+ out_size = Bitcoin.pack_var_int(tx.outputs.size)
38
+
39
+ case hash_type & 0x1f
40
+ when SIGHASH_TYPE[:none]
41
+ outs = ''
42
+ out_size = Bitcoin.pack_var_int(0)
43
+ when SIGHASH_TYPE[:single]
44
+ return "\x01".ljust(32, "\x00") if input_index >= tx.outputs.size
45
+ outs = tx.outputs[0...(input_index + 1)].map.with_index { |o, idx| (idx == input_index) ? o.to_payload : o.to_empty_payload }.join
46
+ out_size = Bitcoin.pack_var_int(input_index + 1)
47
+ end
48
+
49
+ ins = [ins[input_index]] unless hash_type & SIGHASH_TYPE[:anyonecanpay] == 0
50
+
51
+ buf = [[tx.version].pack('V'), Bitcoin.pack_var_int(ins.size),
52
+ ins, out_size, outs, [tx.lock_time, hash_type].pack('VV')].join
53
+
54
+ Bitcoin.double_sha256(buf)
55
+ end
56
+
57
+ end
58
+
59
+ # V0 witness sighash generator.
60
+ # see: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
61
+ class SegwitSigHashGenerator
62
+
63
+ def generate(tx, input_index, hash_type, opts)
64
+ amount = opts[:amount]
65
+ output_script = opts[:script_code]
66
+ skip_separator_index = opts[:skip_separator_index]
67
+ hash_prevouts = Bitcoin.double_sha256(tx.inputs.map{|i|i.out_point.to_payload}.join)
68
+ hash_sequence = Bitcoin.double_sha256(tx.inputs.map{|i|[i.sequence].pack('V')}.join)
69
+ outpoint = tx.inputs[input_index].out_point.to_payload
70
+ amount = [amount].pack('Q')
71
+ nsequence = [tx.inputs[input_index].sequence].pack('V')
72
+ hash_outputs = Bitcoin.double_sha256(tx.outputs.map{|o|o.to_payload}.join)
73
+
74
+ script_code = output_script.to_script_code(skip_separator_index)
75
+
76
+ case (hash_type & 0x1f)
77
+ when SIGHASH_TYPE[:single]
78
+ hash_outputs = input_index >= tx.outputs.size ? "\x00".ljust(32, "\x00") : Bitcoin.double_sha256(tx.outputs[input_index].to_payload)
79
+ hash_sequence = "\x00".ljust(32, "\x00")
80
+ when SIGHASH_TYPE[:none]
81
+ hash_sequence = hash_outputs = "\x00".ljust(32, "\x00")
82
+ end
83
+
84
+ unless (hash_type & SIGHASH_TYPE[:anyonecanpay]) == 0
85
+ hash_prevouts = hash_sequence ="\x00".ljust(32, "\x00")
86
+ end
87
+
88
+ buf = [ [tx.version].pack('V'), hash_prevouts, hash_sequence, outpoint,
89
+ script_code ,amount, nsequence, hash_outputs, [tx.lock_time, hash_type].pack('VV')].join
90
+ Bitcoin.double_sha256(buf)
91
+ end
92
+
93
+ end
94
+
95
+ # v1 witness sighash generator
96
+ # see: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki
97
+ class SchnorrSigHashGenerator
98
+
99
+ # generate signature hash for taproot and tapscript
100
+ # @param [Hash] opts some data using signature. This class requires following key params:
101
+ # - sig_version: sig version. :taproot or :tapscript
102
+ # - prevouts: array of all prevout[Txout]
103
+ # - annex: annex value with binary format if annex exist.
104
+ # - leaf_hash: leaf hash with binary format if sig_version is :tapscript, it required
105
+ # - last_code_separator_pos: the position of last code separator
106
+ # @return [String] signature hash with binary format.
107
+ def generate(tx, input_index, hash_type, opts)
108
+ raise ArgumentError, 'Invalid sig_version was specified.' unless [:taproot, :tapscript].include?(opts[:sig_version])
109
+
110
+ ext_flag = opts[:sig_version] == :taproot ? 0 : 1
111
+ key_version = 0
112
+ output_ype = hash_type == SIGHASH_TYPE[:default] ? SIGHASH_TYPE[:all] : (hash_type & 0x03)
113
+ input_type = hash_type & 0x80
114
+ epoc = '00'.htb
115
+
116
+ buf = epoc # EPOC
117
+ buf << [hash_type, tx.version, tx.lock_time].pack('CVV')
118
+ unless input_type == SIGHASH_TYPE[:anyonecanpay]
119
+ buf << Bitcoin.sha256(tx.in.map{|i|i.out_point.to_payload}.join) # sha_prevouts
120
+ buf << Bitcoin.sha256(opts[:prevouts].map(&:value).pack('Q*'))# sha_amounts
121
+ buf << Bitcoin.sha256(opts[:prevouts].map{|o|o.script_pubkey.to_payload(true)}.join) # sha_scriptpubkeys
122
+ buf << Bitcoin.sha256(tx.in.map(&:sequence).pack('V*')) # sha_sequences
123
+ end
124
+
125
+ buf << Bitcoin.sha256(tx.out.map(&:to_payload).join) if output_ype == SIGHASH_TYPE[:all]
126
+
127
+ spend_type = (ext_flag << 1) + (opts[:annex] ? 1 : 0)
128
+ buf << [spend_type].pack('C')
129
+ if input_type == SIGHASH_TYPE[:anyonecanpay]
130
+ buf << tx.in[input_index].out_point.to_payload
131
+ buf << opts[:prevouts][input_index].to_payload
132
+ buf << [tx.in[input_index].sequence].pack('V')
133
+ else
134
+ buf << [input_index].pack('V')
135
+ end
136
+
137
+ buf << Bitcoin.sha256(Bitcoin.pack_var_string(opts[:annex])) if opts[:annex]
138
+
139
+ if output_ype == SIGHASH_TYPE[:single]
140
+ raise ArgumentError, "Tx does not have #{input_index} th output." if input_index >= tx.out.size
141
+ buf << Bitcoin.sha256(tx.out[input_index].to_payload)
142
+ end
143
+
144
+ if opts[:sig_version] == :tapscript
145
+ buf << opts[:leaf_hash]
146
+ buf << [key_version, opts[:last_code_separator_pos]].pack("CV")
147
+ end
148
+
149
+ Bitcoin.tagged_hash('TapSighash', buf)
150
+ end
151
+
152
+ end
153
+
154
+ end
155
+
156
+ end