bitcoinrb 0.3.1 → 0.7.0

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