bitcoinrb 0.0.1 → 0.1.1

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.
@@ -21,23 +21,59 @@ module Bitcoin
21
21
  script_sig = script_sig.htb
22
22
  hash_type = script_sig[-1].unpack('C').first
23
23
  sig = script_sig[0..-2]
24
- if sig_version == ScriptInterpreter::SIG_VERSION[:witness_v0]
25
- sighash = tx.sighash_for_input(input_index: input_index, hash_type: hash_type,
26
- script_code: script_code, amount: amount, sig_version: sig_version)
27
- else
28
- sighash = tx.sighash_for_input(input_index: input_index, hash_type: hash_type,
29
- script_code: script_code, sig_version: sig_version)
30
- end
31
- key = Bitcoin::Key.new(pubkey: pubkey)
24
+ sighash = tx.sighash_for_input(input_index, script_code, hash_type: hash_type,
25
+ amount: amount, sig_version: sig_version)
26
+ key = Key.new(pubkey: pubkey)
32
27
  key.verify(sig, sighash)
33
28
  end
34
29
 
35
- def check_locktime
36
- # TODO
30
+ def check_locktime(locktime)
31
+ # There are two kinds of nLockTime: lock-by-blockheight and lock-by-blocktime,
32
+ # distinguished by whether nLockTime < LOCKTIME_THRESHOLD.
33
+
34
+ # We want to compare apples to apples, so fail the script unless the type of nLockTime being tested is the same as the nLockTime in the transaction.
35
+ unless ((tx.lock_time < LOCKTIME_THRESHOLD && locktime < LOCKTIME_THRESHOLD) ||
36
+ (tx.lock_time >= LOCKTIME_THRESHOLD && locktime >= LOCKTIME_THRESHOLD))
37
+ return false
38
+ end
39
+
40
+ # Now that we know we're comparing apples-to-apples, the comparison is a simple numeric one.
41
+ return false if locktime > tx.lock_time
42
+
43
+ # Finally the nLockTime feature can be disabled and thus CHECKLOCKTIMEVERIFY bypassed if every txin has been finalized by setting nSequence to maxint.
44
+ # The transaction would be allowed into the blockchain, making the opcode ineffective.
45
+ # Testing if this vin is not final is sufficient to prevent this condition.
46
+ # Alternatively we could test all inputs, but testing just this input minimizes the data required to prove correct CHECKLOCKTIMEVERIFY execution.
47
+ return false if TxIn::SEQUENCE_FINAL == tx.inputs[input_index].sequence
48
+
49
+ true
37
50
  end
38
51
 
39
- def check_sequence
40
- # TODO
52
+ def check_sequence(sequence)
53
+ tx_sequence = tx.inputs[input_index].sequence
54
+ # Fail if the transaction's version number is not set high enough to trigger BIP 68 rules.
55
+ return false if tx.version < 2
56
+
57
+ # Sequence numbers with their most significant bit set are not consensus constrained.
58
+ # Testing that the transaction's sequence number do not have this bit set prevents using this property to get around a CHECKSEQUENCEVERIFY check.
59
+ return false unless tx_sequence & TxIn::SEQUENCE_LOCKTIME_DISABLE_FLAG == 0
60
+
61
+ # Mask off any bits that do not have consensus-enforced meaning before doing the integer comparisons
62
+ locktime_mask = TxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | TxIn::SEQUENCE_LOCKTIME_MASK
63
+ tx_sequence_masked = tx_sequence & locktime_mask
64
+ sequence_masked = sequence & locktime_mask
65
+
66
+ # There are two kinds of nSequence: lock-by-blockheight and lock-by-blocktime,
67
+ # distinguished by whether sequence_masked < TxIn#SEQUENCE_LOCKTIME_TYPE_FLAG.
68
+ # We want to compare apples to apples, so fail the script
69
+ # unless the type of nSequenceMasked being tested is the same as the nSequenceMasked in the transaction.
70
+ unless ((tx_sequence_masked < TxIn::SEQUENCE_LOCKTIME_TYPE_FLAG && sequence_masked < TxIn::SEQUENCE_LOCKTIME_TYPE_FLAG) ||
71
+ (tx_sequence_masked >= TxIn::SEQUENCE_LOCKTIME_TYPE_FLAG && sequence_masked >= TxIn::SEQUENCE_LOCKTIME_TYPE_FLAG))
72
+ return false
73
+ end
74
+
75
+ # Now that we know we're comparing apples-to-apples, the comparison is a simple numeric one.
76
+ sequence_masked <= tx_sequence_masked
41
77
  end
42
78
 
43
79
  end
@@ -1,19 +1,153 @@
1
1
  module Bitcoin
2
2
  module Secp256k1
3
3
 
4
- # secp256k1 module using libsecp256k1
4
+ # binding for secp256k1 (https://github.com/bitcoin/bitcoin/tree/v0.14.2/src/secp256k1)
5
+ # tag: v0.14.2
6
+ # this is not included by default, to enable set shared object path to ENV['SECP256K1_LIB_PATH']
7
+ # for linux, ENV['SECP256K1_LIB_PATH'] = '/usr/local/lib/libsecp256k1.so'
8
+ # for mac,
5
9
  module Native
10
+ include ::FFI::Library
11
+ extend self
6
12
 
13
+ SECP256K1_FLAGS_TYPE_MASK = ((1 << 8) - 1)
14
+ SECP256K1_FLAGS_TYPE_CONTEXT = (1 << 0)
15
+ SECP256K1_FLAGS_TYPE_COMPRESSION = (1 << 1)
16
+
17
+ SECP256K1_FLAGS_BIT_CONTEXT_VERIFY = (1 << 8)
18
+ SECP256K1_FLAGS_BIT_CONTEXT_SIGN = (1 << 9)
19
+ SECP256K1_FLAGS_BIT_COMPRESSION = (1 << 8)
20
+
21
+ # Flags to pass to secp256k1_context_create.
22
+ SECP256K1_CONTEXT_VERIFY = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_VERIFY)
23
+ SECP256K1_CONTEXT_SIGN = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN)
24
+
25
+ # Flag to pass to secp256k1_ec_pubkey_serialize and secp256k1_ec_privkey_export.
26
+ SECP256K1_EC_COMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION)
27
+ SECP256K1_EC_UNCOMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION)
28
+
29
+ module_function
30
+
31
+ def init
32
+ raise 'secp256k1 library dose not found.' unless File.exist?(ENV['SECP256K1_LIB_PATH'])
33
+ ffi_lib(ENV['SECP256K1_LIB_PATH'])
34
+ load_functions
35
+ end
36
+
37
+ def load_functions
38
+ attach_function(:secp256k1_context_create, [:uint], :pointer)
39
+ attach_function(:secp256k1_context_destroy, [:pointer], :void)
40
+ attach_function(:secp256k1_context_randomize, [:pointer, :pointer], :int)
41
+ attach_function(:secp256k1_ec_pubkey_create, [:pointer, :pointer, :pointer], :int)
42
+ attach_function(:secp256k1_ec_seckey_verify, [:pointer, :pointer], :int)
43
+ attach_function(:secp256k1_ecdsa_sign, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
44
+ attach_function(:secp256k1_ec_pubkey_serialize, [:pointer, :pointer, :pointer, :pointer, :uint], :int)
45
+ attach_function(:secp256k1_ecdsa_signature_serialize_der, [:pointer, :pointer, :pointer, :pointer], :int)
46
+ attach_function(:secp256k1_ec_pubkey_parse, [:pointer, :pointer, :pointer, :size_t], :int)
47
+ attach_function(:secp256k1_ecdsa_signature_parse_der, [:pointer, :pointer, :pointer, :size_t], :int)
48
+ attach_function(:secp256k1_ecdsa_signature_normalize, [:pointer, :pointer, :pointer], :int)
49
+ attach_function(:secp256k1_ecdsa_verify, [:pointer, :pointer, :pointer, :pointer], :int)
50
+ end
51
+
52
+ def with_context(flags: (SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN))
53
+ init
54
+ begin
55
+ context = secp256k1_context_create(flags)
56
+ ret, tries, max = 0, 0, 20
57
+ while ret != 1
58
+ raise 'secp256k1_context_randomize failed.' if tries >= max
59
+ tries += 1
60
+ ret = secp256k1_context_randomize(context, FFI::MemoryPointer.from_string(SecureRandom.random_bytes(32)))
61
+ end
62
+ yield(context) if block_given?
63
+ ensure
64
+ secp256k1_context_destroy(context)
65
+ end
66
+ end
67
+
68
+ # generate ec private key and public key
7
69
  def generate_key_pair(compressed: true)
8
- # TODO
70
+ with_context do |context|
71
+ ret, tries, max = 0, 0, 20
72
+ while ret != 1
73
+ raise 'secp256k1_ec_seckey_verify in generate_key_pair failed.' if tries >= max
74
+ tries += 1
75
+ priv_key = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, SecureRandom.random_bytes(32))
76
+ ret = secp256k1_ec_seckey_verify(context, priv_key)
77
+ end
78
+
79
+ internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
80
+ result = secp256k1_ec_pubkey_create(context, internal_pubkey, priv_key)
81
+ raise 'error creating pubkey' unless result
82
+
83
+ pubkey = FFI::MemoryPointer.new(:uchar, 65)
84
+ pubkey_len = FFI::MemoryPointer.new(:uint64)
85
+ result = if compressed
86
+ pubkey_len.put_uint64(0, 33)
87
+ secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_COMPRESSED)
88
+ else
89
+ pubkey_len.put_uint64(0, 65)
90
+ secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_UNCOMPRESSED)
91
+ end
92
+ raise 'error serialize pubkey' unless result || pubkey_len.read_uint64 > 0
93
+
94
+ [ priv_key.read_string(32).bth, pubkey.read_string(pubkey_len.read_uint64).bth ]
95
+ end
9
96
  end
10
97
 
98
+ # generate bitcoin key object
11
99
  def generate_key(compressed: true)
12
- # TODO
100
+ privkey, pubkey = generate_key_pair(compressed: compressed)
101
+ Bitcoin::Key.new(priv_key: privkey, pubkey: pubkey, compressed: compressed)
102
+ end
103
+
104
+ def sign_data(data, priv_key)
105
+ with_context do |context|
106
+ secret = FFI::MemoryPointer.new(:uchar, priv_key.htb.bytesize).put_bytes(0, priv_key.htb)
107
+ raise 'priv_key invalid' unless secp256k1_ec_seckey_verify(context, secret)
108
+
109
+ internal_signature = FFI::MemoryPointer.new(:uchar, 64)
110
+ msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
111
+
112
+ ret, tries, max = 0, 0, 20
113
+ while ret != 1
114
+ raise 'secp256k1_ecdsa_sign failed.' if tries >= max
115
+ tries += 1
116
+ ret = secp256k1_ecdsa_sign(context, internal_signature, msg32, secret, nil, nil)
117
+ end
118
+
119
+ signature = FFI::MemoryPointer.new(:uchar, 72)
120
+ signature_len = FFI::MemoryPointer.new(:uint64).put_uint64(0, 72)
121
+ result = secp256k1_ecdsa_signature_serialize_der(context, signature, signature_len, internal_signature)
122
+ raise 'secp256k1_ecdsa_signature_serialize_der failed' unless result
123
+
124
+ signature.read_string(signature_len.read_uint64)
125
+ end
13
126
  end
14
127
 
15
- def sign_data(privkey, data)
16
- # TODO
128
+ def verify_sig(data, sig, pub_key)
129
+ with_context do |context|
130
+ return false if data.bytesize == 0
131
+
132
+ pubkey = FFI::MemoryPointer.new(:uchar, pub_key.htb.bytesize).put_bytes(0, pub_key.htb)
133
+ internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
134
+ result = secp256k1_ec_pubkey_parse(context, internal_pubkey, pubkey, pubkey.size)
135
+ return false unless result
136
+
137
+ signature = FFI::MemoryPointer.new(:uchar, sig.bytesize).put_bytes(0, sig)
138
+ internal_signature = FFI::MemoryPointer.new(:uchar, 64)
139
+ result = secp256k1_ecdsa_signature_parse_der(context, internal_signature, signature, signature.size)
140
+ #result = ecdsa_signature_parse_der_lax(context, internal_signature, signature, signature.size)
141
+ return false unless result
142
+
143
+ # libsecp256k1's ECDSA verification requires lower-S signatures, which have not historically been enforced in Bitcoin, so normalize them first.
144
+ secp256k1_ecdsa_signature_normalize(context, internal_signature, internal_signature)
145
+
146
+ msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
147
+ result = secp256k1_ecdsa_verify(context, internal_signature, msg32, internal_pubkey)
148
+
149
+ result ? true : false
150
+ end
17
151
  end
18
152
 
19
153
  end
@@ -9,7 +9,7 @@ module Bitcoin
9
9
 
10
10
  module_function
11
11
 
12
- # generate ecdsa private key and public key
12
+ # generate ec private key and public key
13
13
  def generate_key_pair(compressed: true)
14
14
  private_key = 1 + SecureRandom.random_number(GROUP.order - 1)
15
15
  public_key = GROUP.generator.multiply_by_scalar(private_key)
@@ -18,23 +18,12 @@ module Bitcoin
18
18
  [privkey.bth, pubkey.bth]
19
19
  end
20
20
 
21
- # generate bitcoin key
21
+ # generate bitcoin key object
22
22
  def generate_key(compressed: true)
23
23
  privkey, pubkey = generate_key_pair(compressed: compressed)
24
24
  Bitcoin::Key.new(priv_key: privkey, pubkey: pubkey, compressed: compressed)
25
25
  end
26
26
 
27
- # generate publick key from private key
28
- # @param [String] privkey a private key with string format
29
- # @param [Boolean] compressed pubkey compressed?
30
- # @return [String] a pubkey which generate from privkey
31
- def generate_pubkey(privkey, compressed: true)
32
- private_key = ECDSA::Format::IntegerOctetString.decode(privkey.htb)
33
- public_key = GROUP.generator.multiply_by_scalar(private_key)
34
- pubkey = ECDSA::Format::PointOctetString.encode(public_key, compression: compressed)
35
- pubkey.bth
36
- end
37
-
38
27
  # sign data.
39
28
  # @param [String] data a data to be signed
40
29
  # @param [String] privkey a private key using sign
data/lib/bitcoin/tx.rb CHANGED
@@ -3,6 +3,11 @@ module Bitcoin
3
3
  # Transaction class
4
4
  class Tx
5
5
 
6
+ MAX_STANDARD_VERSION = 2
7
+
8
+ # The maximum weight for transactions we're willing to relay/mine
9
+ MAX_STANDARD_TX_WEIGHT = 400000
10
+
6
11
  MARKER = 0x00
7
12
  FLAG = 0x01
8
13
 
@@ -101,22 +106,90 @@ module Bitcoin
101
106
  inputs.map { |i| i.script_witness.to_payload }.join
102
107
  end
103
108
 
109
+ # check this tx is standard.
110
+ def standard?
111
+ return false if version > MAX_STANDARD_VERSION
112
+ return false if weight > MAX_STANDARD_TX_WEIGHT
113
+ inputs.each do |i|
114
+ # Biggest 'standard' txin is a 15-of-15 P2SH multisig with compressed keys (remember the 520 byte limit on redeemScript size).
115
+ # That works out to a (15*(33+1))+3=513 byte redeemScript, 513+1+15*(73+1)+3=1627
116
+ # bytes of scriptSig, which we round off to 1650 bytes for some minor future-proofing.
117
+ # That's also enough to spend a 20-of-20 CHECKMULTISIG scriptPubKey, though such a scriptPubKey is not considered standard.
118
+ return false if i.script_sig.size > 1650
119
+ return false unless i.script_sig.push_only?
120
+ end
121
+ data_count = 0
122
+ outputs.each do |o|
123
+ return false unless o.script_pubkey.standard?
124
+ data_count += 1 if o.script_pubkey.op_return?
125
+ # TODO add non P2SH multisig relay(permitbaremultisig)
126
+ # TODO add dust relay check
127
+ end
128
+ return false if data_count > 1
129
+ true
130
+ end
131
+
132
+ # The serialized transaction size
133
+ def size
134
+ to_payload.bytesize
135
+ end
136
+
137
+ # The virtual transaction size (differs from size for witness transactions)
138
+ def vsize
139
+ (weight.to_f / 4).ceil
140
+ end
141
+
142
+ # calculate tx weight
143
+ # weight = (legacy tx payload) * 3 + (witness tx payload)
144
+ def weight
145
+ if witness?
146
+ serialize_old_format.bytesize * (WITNESS_SCALE_FACTOR - 1) + serialize_witness_format.bytesize
147
+ else
148
+ serialize_old_format.bytesize * WITNESS_SCALE_FACTOR
149
+ end
150
+ end
151
+
104
152
  # get signature hash
105
153
  # @param [Integer] input_index input index.
106
154
  # @param [Integer] hash_type signature hash type
107
- # @param [Bitcoin::Script] script_code script code
155
+ # @param [Bitcoin::Script] output_script script pubkey or script code. if script pubkey is P2WSH, set witness script to this.
108
156
  # @param [Integer] amount bitcoin amount locked in input. required for witness input only.
109
- def sighash_for_input(input_index: nil, hash_type: Script::SIGHASH_TYPE[:all], script_code: nil,
110
- sig_version: ScriptInterpreter::SIG_VERSION[:base], amount: nil)
157
+ # @param [Integer] skip_separator_index If output_script is P2WSH and output_script contains any OP_CODESEPARATOR,
158
+ # the script code needs is the witnessScript but removing everything up to and including the last executed OP_CODESEPARATOR before the signature checking opcode being executed.
159
+ def sighash_for_input(input_index, output_script, hash_type: SIGHASH_TYPE[:all],
160
+ sig_version: :base, amount: nil, skip_separator_index: 0)
111
161
  raise ArgumentError, 'input_index must be specified.' unless input_index
112
162
  raise ArgumentError, 'does not exist input corresponding to input_index.' if input_index >= inputs.size
113
- raise ArgumentError, 'script_pubkey must be specified.' unless script_code
163
+ raise ArgumentError, 'script_pubkey must be specified.' unless output_script
164
+ raise ArgumentError, 'unsupported sig version specified.' unless SIG_VERSION.include?(sig_version)
114
165
 
115
- if sig_version == ScriptInterpreter::SIG_VERSION[:witness_v0]
166
+ if sig_version == :witness_v0
116
167
  raise ArgumentError, 'amount must be specified.' unless amount
117
- sighash_for_witness(input_index, script_code, hash_type, amount)
168
+ sighash_for_witness(input_index, output_script, hash_type, amount, skip_separator_index)
169
+ else
170
+ sighash_for_legacy(input_index, output_script, hash_type)
171
+ end
172
+ end
173
+
174
+ # verify input signature.
175
+ # @param [Integer] input_index
176
+ # @param [Bitcoin::Script] script_pubkey the script pubkey for target input.
177
+ # @param [Integer] amount the amount of bitcoin, require for witness program only.
178
+ # @param [Array] flags the flags used when execute script interpreter.
179
+ def verify_input_sig(input_index, script_pubkey, amount: nil, flags: STANDARD_SCRIPT_VERIFY_FLAGS)
180
+ script_sig = inputs[input_index].script_sig
181
+ has_witness = inputs[input_index].has_witness?
182
+
183
+ if script_pubkey.p2sh?
184
+ flags << SCRIPT_VERIFY_P2SH
185
+ redeem_script = Script.parse_from_payload(script_sig.chunks.last)
186
+ script_pubkey = redeem_script if redeem_script.p2wpkh?
187
+ end
188
+
189
+ if has_witness
190
+ verify_input_sig_for_witness(input_index, script_pubkey, amount, flags)
118
191
  else
119
- sighash_for_legacy(input_index, script_code, hash_type)
192
+ verify_input_sig_for_legacy(input_index, script_pubkey, flags)
120
193
  end
121
194
  end
122
195
 
@@ -126,10 +199,10 @@ module Bitcoin
126
199
  def sighash_for_legacy(index, script_code, hash_type)
127
200
  ins = inputs.map.with_index do |i, idx|
128
201
  if idx == index
129
- i.to_payload(script_code)
202
+ i.to_payload(script_code.delete_opcode(Bitcoin::Opcodes::OP_CODESEPARATOR))
130
203
  else
131
204
  case hash_type & 0x1f
132
- when Script::SIGHASH_TYPE[:none], Script::SIGHASH_TYPE[:single]
205
+ when SIGHASH_TYPE[:none], SIGHASH_TYPE[:single]
133
206
  i.to_payload(Bitcoin::Script.new, 0)
134
207
  else
135
208
  i.to_payload(Bitcoin::Script.new)
@@ -141,16 +214,16 @@ module Bitcoin
141
214
  out_size = Bitcoin.pack_var_int(outputs.size)
142
215
 
143
216
  case hash_type & 0x1f
144
- when Script::SIGHASH_TYPE[:none]
217
+ when SIGHASH_TYPE[:none]
145
218
  outs = ''
146
219
  out_size = Bitcoin.pack_var_int(0)
147
- when Script::SIGHASH_TYPE[:single]
220
+ when SIGHASH_TYPE[:single]
148
221
  return "\x01".ljust(32, "\x00") if index >= outputs.size
149
222
  outs = outputs[0...(index + 1)].map.with_index { |o, idx| (idx == index) ? o.to_payload : o.to_empty_payload }.join
150
223
  out_size = Bitcoin.pack_var_int(index + 1)
151
224
  end
152
225
 
153
- if hash_type & Script::SIGHASH_TYPE[:anyonecanpay] != 0
226
+ if hash_type & SIGHASH_TYPE[:anyonecanpay] != 0
154
227
  ins = [ins[index]]
155
228
  end
156
229
 
@@ -162,7 +235,7 @@ module Bitcoin
162
235
 
163
236
  # generate sighash with BIP-143 format
164
237
  # https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
165
- def sighash_for_witness(index, script_code, hash_type, amount)
238
+ def sighash_for_witness(index, script_pubkey_or_script_code, hash_type, amount, skip_separator_index)
166
239
  hash_prevouts = Bitcoin.double_sha256(inputs.map{|i|i.out_point.to_payload}.join)
167
240
  hash_sequence = Bitcoin.double_sha256(inputs.map{|i|[i.sequence].pack('V')}.join)
168
241
  outpoint = inputs[index].out_point.to_payload
@@ -170,22 +243,46 @@ module Bitcoin
170
243
  nsequence = [inputs[index].sequence].pack('V')
171
244
  hash_outputs = Bitcoin.double_sha256(outputs.map{|o|o.to_payload}.join)
172
245
 
246
+ script_code = script_pubkey_or_script_code.to_script_code(skip_separator_index)
247
+
173
248
  case (hash_type & 0x1f)
174
- when Script::SIGHASH_TYPE[:single]
249
+ when SIGHASH_TYPE[:single]
175
250
  hash_outputs = index >= outputs.size ? "\x00".ljust(32, "\x00") : Bitcoin.double_sha256(outputs[index].to_payload)
176
251
  hash_sequence = "\x00".ljust(32, "\x00")
177
- when Script::SIGHASH_TYPE[:none]
252
+ when SIGHASH_TYPE[:none]
178
253
  hash_sequence = hash_outputs = "\x00".ljust(32, "\x00")
179
254
  end
180
255
 
181
- if (hash_type & Script::SIGHASH_TYPE[:anyonecanpay]) != 0
256
+ if (hash_type & SIGHASH_TYPE[:anyonecanpay]) != 0
182
257
  hash_prevouts = hash_sequence ="\x00".ljust(32, "\x00")
183
258
  end
184
- buf = [ [version].pack('V'), hash_prevouts, hash_sequence, outpoint, Bitcoin::Script.pack_pushdata(script_code.to_payload),
185
- amount, nsequence, hash_outputs, [@lock_time, hash_type].pack('VV')].join
259
+ buf = [ [version].pack('V'), hash_prevouts, hash_sequence, outpoint,
260
+ script_code ,amount, nsequence, hash_outputs, [@lock_time, hash_type].pack('VV')].join
186
261
  Bitcoin.double_sha256(buf)
187
262
  end
188
263
 
264
+ # verify input signature for legacy tx.
265
+ def verify_input_sig_for_legacy(input_index, script_pubkey, flags)
266
+ script_sig = inputs[input_index].script_sig
267
+ checker = Bitcoin::TxChecker.new(tx: self, input_index: input_index)
268
+ interpreter = Bitcoin::ScriptInterpreter.new(checker: checker, flags: flags)
269
+
270
+ interpreter.verify_script(script_sig, script_pubkey)
271
+ end
272
+
273
+ # verify input signature for witness tx.
274
+ def verify_input_sig_for_witness(input_index, script_pubkey, amount, flags)
275
+ flags << SCRIPT_VERIFY_WITNESS
276
+ flags << SCRIPT_VERIFY_WITNESS_PUBKEYTYPE
277
+ checker = Bitcoin::TxChecker.new(tx: self, input_index: input_index, amount: amount)
278
+ interpreter = Bitcoin::ScriptInterpreter.new(checker: checker, flags: flags)
279
+ i = inputs[input_index]
280
+
281
+ script_sig = i.script_sig
282
+ witness = i.script_witness
283
+ interpreter.verify_script(script_sig, script_pubkey, witness)
284
+ end
285
+
189
286
  end
190
287
 
191
288
  end
data/lib/bitcoin/tx_in.rb CHANGED
@@ -8,9 +8,20 @@ module Bitcoin
8
8
  attr_accessor :sequence
9
9
  attr_accessor :script_witness
10
10
 
11
- DEFAULT_SEQUENCE = 0xFFFFFFFF
11
+ # Setting nSequence to this value for every input in a transaction disables nLockTime.
12
+ SEQUENCE_FINAL = 0xffffffff
12
13
 
13
- def initialize(out_point: nil, script_sig: nil, script_witness: ScriptWitness.new, sequence: DEFAULT_SEQUENCE)
14
+ # If this flag set, TxIn#sequence is NOT interpreted as a relative lock-time.
15
+ SEQUENCE_LOCKTIME_DISABLE_FLAG = (1 << 31)
16
+
17
+ # If TxIn#sequence encodes a relative lock-time and this flag is set, the relative lock-time has units of 512 seconds,
18
+ # otherwise it specifies blocks with a granularity of 1.
19
+ SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22)
20
+
21
+ # If TxIn#sequence encodes a relative lock-time, this mask is applied to extract that lock-time from the sequence field.
22
+ SEQUENCE_LOCKTIME_MASK = 0x0000ffff
23
+
24
+ def initialize(out_point: nil, script_sig: Bitcoin::Script.new, script_witness: ScriptWitness.new, sequence: SEQUENCE_FINAL)
14
25
  @out_point = out_point
15
26
  @script_sig = script_sig
16
27
  @script_witness = script_witness
@@ -24,8 +35,11 @@ module Bitcoin
24
35
  i.out_point = OutPoint.new(hash.reverse.bth, index)
25
36
  sig_length = Bitcoin.unpack_var_int_from_io(buf)
26
37
  sig = buf.read(sig_length)
27
- i.script_sig = Script.new
28
- i.script_sig.chunks[0] = sig
38
+ if i.coinbase?
39
+ i.script_sig.chunks[0] = sig
40
+ else
41
+ i.script_sig = Script.parse_from_payload(sig)
42
+ end
29
43
  i.sequence = buf.read(4).unpack('V').first
30
44
  i
31
45
  end
@@ -38,6 +52,11 @@ module Bitcoin
38
52
  p = out_point.to_payload
39
53
  p << Bitcoin.pack_var_int(script_sig.to_payload.bytesize)
40
54
  p << script_sig.to_payload << [sequence].pack('V')
55
+ p
56
+ end
57
+
58
+ def has_witness?
59
+ !script_witness.empty?
41
60
  end
42
61
 
43
62
  end
@@ -13,7 +13,7 @@ module Bitcoin
13
13
 
14
14
  def self.parse_from_payload(payload)
15
15
  buf = payload.is_a?(String) ? StringIO.new(payload) : payload
16
- value = buf.read(8).unpack('Q').first
16
+ value = buf.read(8).unpack('q').first
17
17
  script_size = Bitcoin.unpack_var_int_from_io(buf)
18
18
  new(value: value, script_pubkey: Script.parse_from_payload(buf.read(script_size)))
19
19
  end
@@ -0,0 +1,93 @@
1
+ module Bitcoin
2
+
3
+ class Validation
4
+
5
+ # check transaction validation
6
+ def check_tx(tx, state)
7
+ # Basic checks that don't depend on any context
8
+ if tx.inputs.empty?
9
+ return state.DoS(10, reject_code: Message::Reject::CODE_INVALID, reject_resoin: 'bad-txns-vin-empty')
10
+ end
11
+
12
+ if tx.outputs.empty?
13
+ return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_resoin: 'bad-txns-vout-empty')
14
+ end
15
+
16
+ # Size limits (this doesn't take the witness into account, as that hasn't been checked for malleability)
17
+ if tx.serialize_old_format.bytesize * Bitcoin::WITNESS_SCALE_FACTOR > Bitcoin::MAX_BLOCK_WEIGHT
18
+ return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_resoin: 'bad-txns-oversize')
19
+ end
20
+
21
+ # Check for negative or overflow output values
22
+ amount = 0
23
+ tx.outputs.each do |o|
24
+ return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_resoin: 'bad-txns-vout-negative') if o.value < 0
25
+ return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_resoin: 'bad-txns-vout-toolarge') if MAX_MONEY < o.value
26
+ amount += o.value
27
+ return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_resoin: 'bad-txns-vout-toolarge') if MAX_MONEY < amount
28
+ end
29
+
30
+ # Check for duplicate inputs - note that this check is slow so we skip it in CheckBlock
31
+ out_points = tx.inputs.map{|i|i.out_point.to_payload}
32
+ unless out_points.size == out_points.uniq.size
33
+ return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_resoin: 'bad-txns-inputs-duplicate')
34
+ end
35
+
36
+ if tx.coinbase_tx?
37
+ if tx.inputs[0].script_sig.size < 2 || tx.inputs[0].script_sig.size > 100
38
+ return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_resoin: 'bad-cb-length')
39
+ end
40
+ else
41
+ tx.inputs.each do |i|
42
+ if i.out_point.nil? || !i.out_point.valid?
43
+ return state.DoS(10, reject_code: Message::Reject::CODE_INVALID, reject_resoin: 'bad-txns-prevout-null')
44
+ end
45
+ end
46
+ end
47
+ true
48
+ end
49
+
50
+ end
51
+
52
+ class ValidationState
53
+
54
+ MODE = {valid: 0, invlid: 1, error: 2}
55
+
56
+ attr_accessor :mode
57
+ attr_accessor :n_dos
58
+ attr_accessor :reject_reason
59
+ attr_accessor :reject_code
60
+ attr_accessor :corruption_possible
61
+ attr_accessor :debug_message
62
+
63
+ def initialize
64
+ @mode = MODE[:valid]
65
+ @n_dos = 0
66
+ @reject_code = 0
67
+ @corruption_possible = false
68
+ end
69
+
70
+ def DoS(level, ret: false, reject_code: 0, reject_resoin: '', corruption_in: false, debug_message: '')
71
+ @reject_code = reject_code
72
+ @reject_reason = reject_resoin
73
+ @corruption_possible = corruption_in
74
+ @debug_message = debug_message
75
+ return ret if mode == MODE[:error]
76
+ @n_dos += level
77
+ @mode = MODE[:invalid]
78
+ ret
79
+ end
80
+
81
+ def valid?
82
+ mode == MODE[:valid]
83
+ end
84
+
85
+ def invalid?
86
+ mode == MODE[:invalid]
87
+ end
88
+
89
+ def error?
90
+ mode == MODE[:error]
91
+ end
92
+ end
93
+ end
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.1"
3
3
  end