bitcoinrb 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +5 -4
  4. data/README.md +10 -0
  5. data/bitcoinrb.gemspec +4 -4
  6. data/lib/bitcoin.rb +29 -16
  7. data/lib/bitcoin/block_filter.rb +14 -0
  8. data/lib/bitcoin/chain_params.rb +9 -0
  9. data/lib/bitcoin/chainparams/signet.yml +39 -0
  10. data/lib/bitcoin/constants.rb +43 -3
  11. data/lib/bitcoin/descriptor.rb +1 -1
  12. data/lib/bitcoin/errors.rb +19 -0
  13. data/lib/bitcoin/ext/ecdsa.rb +31 -0
  14. data/lib/bitcoin/ext_key.rb +35 -19
  15. data/lib/bitcoin/key.rb +43 -26
  16. data/lib/bitcoin/message/cfcheckpt.rb +2 -2
  17. data/lib/bitcoin/message/cfheaders.rb +1 -1
  18. data/lib/bitcoin/message/cfilter.rb +1 -1
  19. data/lib/bitcoin/message/fee_filter.rb +1 -1
  20. data/lib/bitcoin/message/filter_load.rb +3 -3
  21. data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
  22. data/lib/bitcoin/message/inventory.rb +1 -1
  23. data/lib/bitcoin/message/merkle_block.rb +1 -1
  24. data/lib/bitcoin/message/network_addr.rb +3 -3
  25. data/lib/bitcoin/message/ping.rb +1 -1
  26. data/lib/bitcoin/message/pong.rb +1 -1
  27. data/lib/bitcoin/message/send_cmpct.rb +2 -2
  28. data/lib/bitcoin/mnemonic.rb +2 -2
  29. data/lib/bitcoin/network/peer_discovery.rb +1 -3
  30. data/lib/bitcoin/node/configuration.rb +3 -1
  31. data/lib/bitcoin/node/spv.rb +8 -0
  32. data/lib/bitcoin/opcodes.rb +14 -1
  33. data/lib/bitcoin/payment_code.rb +2 -2
  34. data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
  35. data/lib/bitcoin/psbt/input.rb +3 -3
  36. data/lib/bitcoin/psbt/output.rb +1 -1
  37. data/lib/bitcoin/psbt/tx.rb +4 -4
  38. data/lib/bitcoin/rpc/bitcoin_core_client.rb +1 -1
  39. data/lib/bitcoin/script/script.rb +52 -19
  40. data/lib/bitcoin/script/script_error.rb +27 -1
  41. data/lib/bitcoin/script/script_interpreter.rb +161 -62
  42. data/lib/bitcoin/script/tx_checker.rb +64 -14
  43. data/lib/bitcoin/secp256k1/native.rb +138 -25
  44. data/lib/bitcoin/secp256k1/ruby.rb +78 -19
  45. data/lib/bitcoin/sighash_generator.rb +156 -0
  46. data/lib/bitcoin/tx.rb +13 -80
  47. data/lib/bitcoin/tx_in.rb +1 -1
  48. data/lib/bitcoin/tx_out.rb +2 -3
  49. data/lib/bitcoin/util.rb +15 -6
  50. data/lib/bitcoin/version.rb +1 -1
  51. data/lib/bitcoin/wallet/account.rb +1 -1
  52. metadata +19 -15
@@ -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
@@ -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
@@ -27,11 +27,80 @@ module Bitcoin
27
27
  public_key.to_hex(compressed)
28
28
  end
29
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
42
+ end
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 ||= ''
@@ -59,32 +128,22 @@ 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
149
  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