btcruby 1.1.1 → 1.1.2
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.
- checksums.yaml +4 -4
- data/README.md +3 -1
- data/RELEASE_NOTES.md +8 -0
- data/lib/btcruby.rb +6 -1
- data/lib/btcruby/openssl.rb +23 -9
- data/lib/btcruby/{transaction_outpoint.rb → outpoint.rb} +14 -1
- data/lib/btcruby/script/cltv_plugin.rb +63 -0
- data/lib/btcruby/script/p2sh_plugin.rb +71 -0
- data/lib/btcruby/script/script.rb +7 -1
- data/lib/btcruby/script/script_error.rb +1 -1
- data/lib/btcruby/script/script_interpreter.rb +230 -120
- data/lib/btcruby/script/script_interpreter_plugin.rb +67 -0
- data/lib/btcruby/script/script_number.rb +2 -2
- data/lib/btcruby/script/transaction_signature_checker.rb +4 -1
- data/lib/btcruby/secp256k1.rb +77 -0
- data/lib/btcruby/transaction.rb +5 -4
- data/lib/btcruby/transaction_input.rb +15 -2
- data/lib/btcruby/transaction_output.rb +2 -2
- data/lib/btcruby/validation.rb +90 -0
- data/lib/btcruby/version.rb +1 -1
- data/spec/data/script_invalid.rb +814 -0
- data/spec/data/script_valid.rb +911 -0
- data/spec/data/tx_invalid.rb +197 -0
- data/spec/data/tx_valid.rb +233 -0
- data/spec/open_assets/issuance_id_spec.rb +1 -1
- data/spec/script_interpreter_spec.rb +85 -0
- data/spec/secp256k1_spec.rb +33 -0
- data/spec/spec_helper.rb +115 -0
- data/spec/transaction_spec.rb +153 -0
- metadata +16 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e26eccf4e25b07bfb7c0a20d4757c73112ef36ac
|
4
|
+
data.tar.gz: 44f3ca44eb7f7c36e666a7a0987055c5a16efeb7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c891533a5c79e2091f095ae8a9c103cf326cb5d6f532e62497dfc677e887ddf3b718cee7fc16f5b86c53b45b7f9a08320942b088cb55d92ec246aadf84c0f7b
|
7
|
+
data.tar.gz: 8c0dda6751152a16cf585c5472e5f9bb6ee0c0f2bca7fc64ff3e204ea85b9a93924c2d70e88ed70c20e04f36dfec04bc4032633c4ba0c99854fb6fee56c4b823
|
data/README.md
CHANGED
@@ -28,6 +28,7 @@ Please see [BTCRuby Reference](documentation/index.md) for API documentation and
|
|
28
28
|
and [BIP62](https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki).
|
29
29
|
* Automatic normalization of existing ECDSA signatures (see `BTC::Key#normalized_signature`).
|
30
30
|
* Rich script analysis and compositing support (see `BTC::Script`).
|
31
|
+
* Full script interpreter with [P2SH](https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki) and [CLTV](https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki) features.
|
31
32
|
* Powerful diagnostics API covering the entire library (see `BTC::Diagnostics`).
|
32
33
|
* Canonicality checks for transactions, public keys and script elements.
|
33
34
|
* Fee computation and signature script simulation for building transactions without signing them.
|
@@ -36,7 +37,7 @@ Please see [BTCRuby Reference](documentation/index.md) for API documentation and
|
|
36
37
|
## Philosophy
|
37
38
|
|
38
39
|
* We use clear, expressive names for all methods and classes.
|
39
|
-
* Self-contained implementation. Only external dependency is `ffi` gem that helps linking directly with OpenSSL.
|
40
|
+
* Self-contained implementation. Only external dependency is `ffi` gem that helps linking directly with OpenSSL and libsecp256k1.
|
40
41
|
* For efficiency and consistency we use binary strings throughout the library (not the hex strings as in other libraries).
|
41
42
|
* We do not pollute standard classes with our methods. To use utility extensions like `String#to_hex` you should explicitly `require 'btcruby/extensions'`.
|
42
43
|
* We use OpenSSL `BIGNUM` implementation where compatibility is critical (instead of the built-in Ruby Bignum).
|
@@ -49,6 +50,7 @@ The goal is to provide a complete Bitcoin toolkit in Ruby.
|
|
49
50
|
|
50
51
|
```
|
51
52
|
$ bundle install
|
53
|
+
$ brew install ./vendor/homebrew/secp256k1.rb
|
52
54
|
$ rake
|
53
55
|
```
|
54
56
|
|
data/RELEASE_NOTES.md
CHANGED
@@ -2,6 +2,14 @@
|
|
2
2
|
BTCRuby Release Notes
|
3
3
|
=====================
|
4
4
|
|
5
|
+
1.1.2 (August 17, 2015)
|
6
|
+
---------------------
|
7
|
+
|
8
|
+
* Added scripts test suite from Bitcoin Core.
|
9
|
+
* Added transactions test suite from Bitcoin Core.
|
10
|
+
* As a result, fixed a few consensus bugs.
|
11
|
+
* Renamed `TransactionOutpoint` to `Outpoint` (previous name kept for backwards compatibility).
|
12
|
+
|
5
13
|
1.1.1 (July 30, 2015)
|
6
14
|
---------------------
|
7
15
|
|
data/lib/btcruby.rb
CHANGED
@@ -23,10 +23,11 @@ require_relative 'btcruby/key.rb'
|
|
23
23
|
require_relative 'btcruby/keychain.rb'
|
24
24
|
require_relative 'btcruby/wire_format.rb'
|
25
25
|
require_relative 'btcruby/hash_id.rb'
|
26
|
+
require_relative 'btcruby/outpoint.rb'
|
26
27
|
require_relative 'btcruby/transaction.rb'
|
27
28
|
require_relative 'btcruby/transaction_input.rb'
|
28
29
|
require_relative 'btcruby/transaction_output.rb'
|
29
|
-
require_relative 'btcruby/
|
30
|
+
require_relative 'btcruby/validation.rb'
|
30
31
|
|
31
32
|
require_relative 'btcruby/script/script_error.rb'
|
32
33
|
require_relative 'btcruby/script/script_flags.rb'
|
@@ -39,6 +40,10 @@ require_relative 'btcruby/script/signature_checker.rb'
|
|
39
40
|
require_relative 'btcruby/script/test_signature_checker.rb'
|
40
41
|
require_relative 'btcruby/script/transaction_signature_checker.rb'
|
41
42
|
|
43
|
+
require_relative 'btcruby/script/script_interpreter_plugin.rb'
|
44
|
+
require_relative 'btcruby/script/p2sh_plugin.rb'
|
45
|
+
require_relative 'btcruby/script/cltv_plugin.rb'
|
46
|
+
|
42
47
|
require_relative 'btcruby/transaction_builder.rb'
|
43
48
|
require_relative 'btcruby/proof_of_work.rb'
|
44
49
|
require_relative 'btcruby/block_header.rb'
|
data/lib/btcruby/openssl.rb
CHANGED
@@ -269,7 +269,7 @@ module BTC
|
|
269
269
|
# Nonce k is equal to HMAC-SHA256(data: hash, key: privkey)
|
270
270
|
def ecdsa_signature(hash, privkey, normalized: true)
|
271
271
|
raise ArgumentError, "Hash is missing" if !hash
|
272
|
-
raise ArgumentError, "
|
272
|
+
raise ArgumentError, "Private key is missing" if !privkey
|
273
273
|
|
274
274
|
# ECDSA signature is a pair of numbers: (Kx, s)
|
275
275
|
# Where Kx = x coordinate of k*G mod n (n is the order of secp256k1).
|
@@ -345,6 +345,10 @@ module BTC
|
|
345
345
|
# Normalizes S value of the signature and returns normalized signature.
|
346
346
|
# Returns nil if signature is completely invalid.
|
347
347
|
def ecdsa_normalized_signature(signature)
|
348
|
+
ecdsa_reserialize_signature(signature, normalize_s: true)
|
349
|
+
end
|
350
|
+
|
351
|
+
def ecdsa_reserialize_signature(signature, normalize_s: false)
|
348
352
|
raise ArgumentError, "Signature is missing" if !signature
|
349
353
|
|
350
354
|
autorelease do |pool|
|
@@ -358,15 +362,19 @@ module BTC
|
|
358
362
|
buf = FFI::MemoryPointer.from_string(signature)
|
359
363
|
psig = d2i_ECDSA_SIG(nil, pointer_to_pointer(buf), buf.size-1)
|
360
364
|
if psig.null?
|
361
|
-
|
365
|
+
Diagnostics.current.add_message("OpenSSL failed to read ECDSA signature with DER during reserialize: #{BTC.to_hex(signature).inspect}")
|
366
|
+
return signature
|
367
|
+
#raise BTCError, "OpenSSL failed to read ECDSA signature with DER: #{BTC.to_hex(signature).inspect}"
|
362
368
|
end
|
363
369
|
|
364
370
|
sig = ECDSA_SIG.new(psig) # read sig from its pointer
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
371
|
+
|
372
|
+
if normalize_s
|
373
|
+
# Enforce low S values, by negating the value (modulo the order) if above order/2.
|
374
|
+
s = sig[:s]
|
375
|
+
if BN_cmp(s, halfn) > 0
|
376
|
+
BN_sub(s, n, s)
|
377
|
+
end
|
370
378
|
end
|
371
379
|
|
372
380
|
# Note: we'll place new s value back to s bignum,
|
@@ -393,6 +401,9 @@ module BTC
|
|
393
401
|
raise ArgumentError, "Signature is missing" if !signature
|
394
402
|
raise ArgumentError, "Hash is missing" if !hash
|
395
403
|
raise ArgumentError, "Public key is missing" if !public_key
|
404
|
+
|
405
|
+
# New versions of OpenSSL will reject non-canonical DER signatures. de/re-serialize first.
|
406
|
+
signature = ecdsa_reserialize_signature(signature, normalize_s: false)
|
396
407
|
|
397
408
|
autorelease do |pool|
|
398
409
|
eckey = pool.new_ec_key
|
@@ -400,13 +411,14 @@ module BTC
|
|
400
411
|
buf = FFI::MemoryPointer.from_string(public_key)
|
401
412
|
eckey = o2i_ECPublicKey(pointer_to_pointer(eckey), pointer_to_pointer(buf), buf.size - 1)
|
402
413
|
if eckey.null?
|
414
|
+
Diagnostics.current.add_message("OpenSSL failed to create EC_KEY with public key: #{BTC.to_hex(public_key).inspect}")
|
403
415
|
raise BTCError, "OpenSSL failed to create EC_KEY with public key: #{BTC.to_hex(public_key).inspect}"
|
404
416
|
end
|
405
417
|
|
406
418
|
# -1 = error, 0 = bad sig, 1 = good
|
407
419
|
hash_buf = FFI::MemoryPointer.from_string(hash)
|
408
420
|
sig_buf = FFI::MemoryPointer.from_string(signature)
|
409
|
-
result = ECDSA_verify(0, hash_buf,
|
421
|
+
result = ECDSA_verify(0, hash_buf, hash.bytesize, sig_buf, signature.bytesize, eckey)
|
410
422
|
|
411
423
|
if result == 1
|
412
424
|
return true
|
@@ -415,7 +427,9 @@ module BTC
|
|
415
427
|
if result == 0
|
416
428
|
Diagnostics.current.add_message("OpenSSL detected invalid ECDSA signature. Signature: #{BTC.to_hex(signature).inspect}; Hash: #{BTC.to_hex(hash).inspect}; Pubkey: #{BTC.to_hex(public_key).inspect}")
|
417
429
|
else
|
418
|
-
|
430
|
+
Diagnostics.current.add_message("OpenSSL failed with error while verifying ECDSA signature. Signature: #{BTC.to_hex(signature).inspect}; Hash: #{BTC.to_hex(hash).inspect}; Pubkey: #{BTC.to_hex(public_key).inspect}; Result: #{result}")
|
431
|
+
return false
|
432
|
+
# raise BTCError, "OpenSSL failed with error while verifying ECDSA signature. Signature: #{BTC.to_hex(signature).inspect}; Hash: #{BTC.to_hex(hash).inspect}; Pubkey: #{BTC.to_hex(public_key).inspect}; Result: #{result}"
|
419
433
|
end
|
420
434
|
return false
|
421
435
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
module BTC
|
2
2
|
# Represents a reference to a previous transaction.
|
3
|
-
class
|
3
|
+
class Outpoint
|
4
|
+
INVALID_INDEX = 0xFFFFFFFF # aka "(unsigned int) -1" in BitcoinQT.
|
5
|
+
ZERO_HASH256 = ("\x00".b*32).freeze
|
6
|
+
|
4
7
|
attr_accessor :transaction_hash
|
5
8
|
attr_accessor :transaction_id
|
6
9
|
attr_accessor :index
|
@@ -8,6 +11,9 @@ module BTC
|
|
8
11
|
def initialize(transaction_hash: nil, transaction_id: nil, index: 0)
|
9
12
|
@transaction_hash = transaction_hash
|
10
13
|
self.transaction_id = transaction_id if transaction_id
|
14
|
+
while index < 0
|
15
|
+
index += 2**32
|
16
|
+
end
|
11
17
|
@index = index
|
12
18
|
end
|
13
19
|
|
@@ -22,6 +28,10 @@ module BTC
|
|
22
28
|
def outpoint_id
|
23
29
|
%{#{transaction_id}:#{index}}
|
24
30
|
end
|
31
|
+
|
32
|
+
def null?
|
33
|
+
@index == INVALID_INDEX && @transaction_hash == ZERO_HASH256
|
34
|
+
end
|
25
35
|
|
26
36
|
def ==(other)
|
27
37
|
index == other.index &&
|
@@ -37,4 +47,7 @@ module BTC
|
|
37
47
|
outpoint_id
|
38
48
|
end
|
39
49
|
end
|
50
|
+
|
51
|
+
# For backwards compatibility keep the longer name.
|
52
|
+
TransactionOutpoint = Outpoint
|
40
53
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module BTC
|
2
|
+
# Performs CHECKLOCKTIMEVERIFY (BIP65) evaluation
|
3
|
+
class CLTVPlugin
|
4
|
+
include ScriptInterpreterPlugin
|
5
|
+
|
6
|
+
# Default `locktime_max_size` is 5.
|
7
|
+
# Default `lock_time_checker` equals current interpreter's signature checker.
|
8
|
+
def initialize(locktime_max_size: nil, lock_time_checker: nil)
|
9
|
+
@locktime_max_size = locktime_max_size || 5
|
10
|
+
@lock_time_checker = lock_time_checker
|
11
|
+
end
|
12
|
+
|
13
|
+
def extra_flags
|
14
|
+
SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY
|
15
|
+
end
|
16
|
+
|
17
|
+
def should_handle_opcode(interpreter: nil, opcode: nil)
|
18
|
+
opcode == OP_CHECKLOCKTIMEVERIFY
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns `false` if failed to execute the opcode.
|
22
|
+
def handle_opcode(interpreter: nil, opcode: nil)
|
23
|
+
# We are not supposed to handle any other opcodes here.
|
24
|
+
return false if opcode != OP_CHECKLOCKTIMEVERIFY
|
25
|
+
|
26
|
+
if interpreter.stack.size < 1
|
27
|
+
return interpreter.set_error(SCRIPT_ERR_INVALID_STACK_OPERATION)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Note that elsewhere numeric opcodes are limited to
|
31
|
+
# operands in the range -2**31+1 to 2**31-1, however it is
|
32
|
+
# legal for opcodes to produce results exceeding that
|
33
|
+
# range. This limitation is implemented by CScriptNum's
|
34
|
+
# default 4-byte limit.
|
35
|
+
#
|
36
|
+
# If we kept to that limit we'd have a year 2038 problem,
|
37
|
+
# even though the nLockTime field in transactions
|
38
|
+
# themselves is uint32 which only becomes meaningless
|
39
|
+
# after the year 2106.
|
40
|
+
#
|
41
|
+
# Thus as a special case we tell CScriptNum to accept up
|
42
|
+
# to 5-byte bignums, which are good until 2**39-1, well
|
43
|
+
# beyond the 2**32-1 limit of the nLockTime field itself.
|
44
|
+
locktime = interpreter.cast_to_number(interpreter.stack.last, max_size: @locktime_max_size)
|
45
|
+
|
46
|
+
# In the rare event that the argument may be < 0 due to
|
47
|
+
# some arithmetic being done first, you can always use
|
48
|
+
# 0 MAX CHECKLOCKTIMEVERIFY.
|
49
|
+
if locktime < 0
|
50
|
+
return interpreter.set_error(SCRIPT_ERR_NEGATIVE_LOCKTIME)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Actually compare the specified lock time with the transaction.
|
54
|
+
checker = @lock_time_checker || interpreter.signature_checker
|
55
|
+
if !checker.check_lock_time(locktime)
|
56
|
+
return interpreter.set_error(SCRIPT_ERR_UNSATISFIED_LOCKTIME)
|
57
|
+
end
|
58
|
+
|
59
|
+
return true
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module BTC
|
2
|
+
# Performs Pay-to-Script-Hash (BIP16) evaluation
|
3
|
+
class P2SHPlugin
|
4
|
+
include ScriptInterpreterPlugin
|
5
|
+
|
6
|
+
# Returns additional flags to be available to #flag? checks during script execution.
|
7
|
+
# This way one plugin can affect evaluation of another.
|
8
|
+
def extra_flags
|
9
|
+
SCRIPT_VERIFY_P2SH
|
10
|
+
end
|
11
|
+
|
12
|
+
# Every plugin gets this callback. If plugin return `false`, execution is stopped and interpreter returns `false`.
|
13
|
+
# Default value is `true`.
|
14
|
+
def did_execute_signature_script(interpreter: nil, signature_script: nil)
|
15
|
+
@signature_script = signature_script
|
16
|
+
@stack_copy = interpreter.stack.dup
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def did_execute_output_script(interpreter: nil, output_script: nil)
|
21
|
+
# Cleanup ivars
|
22
|
+
stack_copy = @stack_copy
|
23
|
+
@stack_copy = nil
|
24
|
+
|
25
|
+
signature_script = @signature_script
|
26
|
+
@signature_script = nil
|
27
|
+
|
28
|
+
# If output script is not P2SH, do nothing.
|
29
|
+
return true if !output_script.p2sh?
|
30
|
+
|
31
|
+
# Additional validation for pay-to-script-hash (P2SH) transactions:
|
32
|
+
|
33
|
+
# scriptSig must be literals-only or validation fails
|
34
|
+
if !signature_script.data_only?
|
35
|
+
return interpreter.set_error(SCRIPT_ERR_SIG_PUSHONLY)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Restore stack.
|
39
|
+
interpreter.stack = stack_copy
|
40
|
+
|
41
|
+
# stack cannot be empty here, because if it was the
|
42
|
+
# P2SH HASH <> EQUAL scriptPubKey would be evaluated with
|
43
|
+
# an empty stack and the EvalScript above would return false.
|
44
|
+
raise "Stack cannot be empty" if stack_copy.empty?
|
45
|
+
|
46
|
+
serialized_redeem_script = interpreter.stack.pop
|
47
|
+
begin
|
48
|
+
redeem_script = BTC::Script.new(data: serialized_redeem_script)
|
49
|
+
rescue => e
|
50
|
+
return interpreter.set_error(SCRIPT_ERR_BAD_OPCODE, "Failed to parse serialized redeem script for P2SH. #{e.message}")
|
51
|
+
end
|
52
|
+
|
53
|
+
# Actually execute the script.
|
54
|
+
if !interpreter.run_script(redeem_script)
|
55
|
+
# error is set in run_script
|
56
|
+
return false
|
57
|
+
end
|
58
|
+
|
59
|
+
if interpreter.stack.empty?
|
60
|
+
return interpreter.set_error(SCRIPT_ERR_EVAL_FALSE)
|
61
|
+
end
|
62
|
+
|
63
|
+
if interpreter.cast_to_bool(interpreter.stack.last) == false
|
64
|
+
return interpreter.set_error(SCRIPT_ERR_EVAL_FALSE)
|
65
|
+
end
|
66
|
+
|
67
|
+
return true
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -421,9 +421,11 @@ module BTC
|
|
421
421
|
|
422
422
|
# Appends an opcode (Integer), pushdata (String) or Script and returns self.
|
423
423
|
# If Array is passed, this method is recursively called for each element in the array.
|
424
|
-
def
|
424
|
+
def append(object)
|
425
425
|
if object.is_a?(BTC::Script)
|
426
426
|
append_script(object)
|
427
|
+
elsif object.is_a?(BTC::ScriptNumber)
|
428
|
+
append_pushdata(object.data)
|
427
429
|
elsif object.is_a?(Integer)
|
428
430
|
append_opcode(object)
|
429
431
|
elsif object.is_a?(String)
|
@@ -439,6 +441,10 @@ module BTC
|
|
439
441
|
end
|
440
442
|
return self
|
441
443
|
end
|
444
|
+
|
445
|
+
def <<(object)
|
446
|
+
append(object)
|
447
|
+
end
|
442
448
|
|
443
449
|
# Returns a new instance with concatenation of two scripts.
|
444
450
|
def +(other)
|
@@ -13,51 +13,88 @@ module BTC
|
|
13
13
|
class ScriptInterpreter
|
14
14
|
include ScriptFlags
|
15
15
|
|
16
|
+
# Flags specified for this interpreter, not including flags added by plugins.
|
16
17
|
attr_accessor :flags
|
18
|
+
attr_accessor :plugins
|
17
19
|
attr_accessor :signature_checker
|
20
|
+
attr_accessor :raise_on_failure
|
21
|
+
attr_accessor :max_pushdata_size
|
22
|
+
attr_accessor :max_op_count
|
23
|
+
attr_accessor :max_stack_size
|
24
|
+
attr_accessor :max_script_size
|
25
|
+
attr_accessor :integer_max_size
|
26
|
+
attr_accessor :locktime_max_size
|
27
|
+
|
28
|
+
# Execution state
|
18
29
|
attr_accessor :stack
|
19
|
-
|
30
|
+
attr_reader :altstack
|
20
31
|
attr_accessor :error # ScriptError instance
|
21
32
|
|
22
33
|
# Instantiates interpreter with validation flags and an optional checker
|
23
34
|
# (required if the scripts use signature-checking opcodes).
|
24
35
|
# Checker can be transaction checker or block checker
|
25
36
|
def initialize(flags: SCRIPT_VERIFY_NONE,
|
37
|
+
plugins: nil,
|
26
38
|
signature_checker: nil,
|
27
39
|
raise_on_failure: false,
|
28
40
|
max_pushdata_size: MAX_SCRIPT_ELEMENT_SIZE,
|
29
41
|
max_op_count: MAX_OPS_PER_SCRIPT,
|
30
42
|
max_stack_size: MAX_STACK_SIZE,
|
43
|
+
max_script_size: MAX_SCRIPT_SIZE,
|
31
44
|
integer_max_size: 4,
|
32
45
|
locktime_max_size: 5)
|
33
46
|
@flags = flags
|
47
|
+
@plugins = plugins || []
|
34
48
|
@signature_checker = signature_checker
|
49
|
+
|
35
50
|
@raise_on_failure = raise_on_failure
|
36
51
|
@max_pushdata_size = max_pushdata_size
|
37
52
|
@max_op_count = max_op_count
|
38
53
|
@max_stack_size = max_stack_size
|
54
|
+
@max_script_size = max_script_size
|
39
55
|
@integer_max_size = integer_max_size
|
40
56
|
@locktime_max_size = locktime_max_size
|
41
|
-
|
42
|
-
@stack = []
|
43
|
-
@altstack = []
|
44
57
|
end
|
45
58
|
|
46
59
|
# Returns true if succeeded or false in case of failure.
|
47
60
|
# If fails, sets the error attribute.
|
48
61
|
def verify_script(signature_script: nil, output_script: nil)
|
49
62
|
|
63
|
+
@stack = []
|
64
|
+
@altstack = []
|
65
|
+
|
50
66
|
if flag?(SCRIPT_VERIFY_SIGPUSHONLY) && !signature_script.data_only?
|
51
67
|
return set_error(SCRIPT_ERR_SIG_PUSHONLY)
|
52
68
|
end
|
53
69
|
|
70
|
+
if plugin = plugin_to_handle_scripts(signature_script, output_script)
|
71
|
+
return plugin.handle_scripts(
|
72
|
+
interpreter: self,
|
73
|
+
signature_script: signature_script,
|
74
|
+
output_script: output_script
|
75
|
+
) && verify_clean_stack_if_needed
|
76
|
+
end
|
77
|
+
|
54
78
|
if !run_script(signature_script)
|
55
79
|
# error is set in run_script
|
56
80
|
return false
|
57
81
|
end
|
58
82
|
|
59
|
-
|
60
|
-
|
83
|
+
if !did_execute_signature_script(signature_script)
|
84
|
+
# error is set already
|
85
|
+
return false
|
86
|
+
end
|
87
|
+
|
88
|
+
# This is implemented in P2SHPlugin
|
89
|
+
# stack_copy = if flag?(SCRIPT_VERIFY_P2SH)
|
90
|
+
# @stack.dup
|
91
|
+
# end
|
92
|
+
|
93
|
+
if plugin = plugin_to_handle_output_script(output_script)
|
94
|
+
return plugin.handle_output_script(
|
95
|
+
interpreter: self,
|
96
|
+
output_script: output_script
|
97
|
+
) && verify_clean_stack_if_needed
|
61
98
|
end
|
62
99
|
|
63
100
|
if !run_script(output_script)
|
@@ -73,58 +110,15 @@ module BTC
|
|
73
110
|
return set_error(SCRIPT_ERR_EVAL_FALSE)
|
74
111
|
end
|
75
112
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
# scriptSig must be literals-only or validation fails
|
80
|
-
if !signature_script.data_only?
|
81
|
-
return set_error(SCRIPT_ERR_SIG_PUSHONLY)
|
82
|
-
end
|
83
|
-
|
84
|
-
# Restore stack.
|
85
|
-
@stack = stack_copy
|
86
|
-
|
87
|
-
# stack cannot be empty here, because if it was the
|
88
|
-
# P2SH HASH <> EQUAL scriptPubKey would be evaluated with
|
89
|
-
# an empty stack and the EvalScript above would return false.
|
90
|
-
raise "Stack cannot be empty" if @stack.empty?
|
91
|
-
|
92
|
-
serialized_redeem_script = stack_pop
|
93
|
-
begin
|
94
|
-
redeem_script = BTC::Script.new(data: serialized_redeem_script)
|
95
|
-
rescue => e
|
96
|
-
return set_error(SCRIPT_ERR_BAD_OPCODE, "Failed to parse serialized redeem script for P2SH. #{e.message}")
|
97
|
-
end
|
98
|
-
|
99
|
-
if !run_script(redeem_script)
|
100
|
-
# error is set in run_script
|
101
|
-
return false
|
102
|
-
end
|
103
|
-
|
104
|
-
if @stack.empty?
|
105
|
-
return set_error(SCRIPT_ERR_EVAL_FALSE)
|
106
|
-
end
|
107
|
-
|
108
|
-
if cast_to_bool(@stack.last) == false
|
109
|
-
return set_error(SCRIPT_ERR_EVAL_FALSE)
|
110
|
-
end
|
113
|
+
if !did_execute_output_script(output_script)
|
114
|
+
# error is set already
|
115
|
+
return false
|
111
116
|
end
|
112
117
|
|
113
|
-
#
|
114
|
-
#
|
115
|
-
|
116
|
-
|
117
|
-
# Disallow CLEANSTACK without P2SH, as otherwise a switch CLEANSTACK->P2SH+CLEANSTACK
|
118
|
-
# would be possible, which is not a softfork (and P2SH should be one).
|
119
|
-
if !flag?(SCRIPT_VERIFY_P2SH)
|
120
|
-
raise ArgumentError, "CLEANSTACK without P2SH is disallowed"
|
121
|
-
end
|
122
|
-
if @stack.size != 1
|
123
|
-
return set_error(SCRIPT_ERR_CLEANSTACK, "Stack must be clean (should contain one item 'true')")
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
return true
|
118
|
+
# Additional validation for pay-to-script-hash (P2SH) transactions:
|
119
|
+
# See P2SHPlugin#did_execute_output_script.
|
120
|
+
|
121
|
+
return verify_clean_stack_if_needed
|
128
122
|
end
|
129
123
|
|
130
124
|
|
@@ -140,6 +134,13 @@ module BTC
|
|
140
134
|
# Used internally in `verify_script` and also in unit tests.
|
141
135
|
def run_script(script)
|
142
136
|
|
137
|
+
if script.data.bytesize > @max_script_size
|
138
|
+
return set_error(SCRIPT_ERR_SCRIPT_SIZE)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Altstack is not shared between individual runs, but we still store it in ivar to make available to the plugins.
|
142
|
+
@altstack = []
|
143
|
+
|
143
144
|
number_zero = ScriptNumber.new(integer: 0)
|
144
145
|
number_one = ScriptNumber.new(integer: 1)
|
145
146
|
zero_value = "".b
|
@@ -165,32 +166,45 @@ module BTC
|
|
165
166
|
return set_error(SCRIPT_ERR_OP_COUNT)
|
166
167
|
end
|
167
168
|
end
|
168
|
-
|
169
|
-
if opcode
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
169
|
+
|
170
|
+
# Check if there is a plugin for this opcode before we check for disabled opcodes.
|
171
|
+
opcode_plugin = plugin_to_handle_opcode(opcode)
|
172
|
+
|
173
|
+
if !opcode_plugin
|
174
|
+
if opcode == OP_CAT ||
|
175
|
+
opcode == OP_SUBSTR ||
|
176
|
+
opcode == OP_LEFT ||
|
177
|
+
opcode == OP_RIGHT ||
|
178
|
+
opcode == OP_INVERT ||
|
179
|
+
opcode == OP_AND ||
|
180
|
+
opcode == OP_OR ||
|
181
|
+
opcode == OP_XOR ||
|
182
|
+
opcode == OP_2MUL ||
|
183
|
+
opcode == OP_2DIV ||
|
184
|
+
opcode == OP_MUL ||
|
185
|
+
opcode == OP_DIV ||
|
186
|
+
opcode == OP_MOD ||
|
187
|
+
opcode == OP_LSHIFT ||
|
188
|
+
opcode == OP_RSHIFT
|
189
|
+
|
190
|
+
return set_error(SCRIPT_ERR_DISABLED_OPCODE)
|
191
|
+
end
|
186
192
|
end
|
187
193
|
|
188
194
|
if should_execute && 0 <= opcode && opcode <= OP_PUSHDATA4
|
189
195
|
# Pushdata (including OP_0).
|
190
|
-
if require_minimal && !chunk.
|
196
|
+
if require_minimal && !check_minimal_push(chunk.pushdata, opcode)
|
191
197
|
return set_error(SCRIPT_ERR_MINIMALDATA)
|
192
198
|
end
|
193
199
|
stack_push(chunk.pushdata)
|
200
|
+
|
201
|
+
elsif should_execute && opcode_plugin
|
202
|
+
|
203
|
+
if !opcode_plugin.handle_opcode(interpreter: self, opcode: opcode)
|
204
|
+
# error is set already
|
205
|
+
return false
|
206
|
+
end
|
207
|
+
|
194
208
|
elsif should_execute || (OP_IF <= opcode && opcode <= OP_ENDIF)
|
195
209
|
|
196
210
|
case opcode
|
@@ -207,47 +221,50 @@ module BTC
|
|
207
221
|
|
208
222
|
when OP_NOP
|
209
223
|
# nothing
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
224
|
+
|
225
|
+
# See CLTVPlugin
|
226
|
+
# when OP_CHECKLOCKTIMEVERIFY
|
227
|
+
# begin
|
228
|
+
# if !flag?(SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY)
|
229
|
+
# # not enabled; treat as a NOP2
|
230
|
+
# if flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS)
|
231
|
+
# return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS)
|
232
|
+
# end
|
233
|
+
# break # breaks out of begin ... end while false
|
234
|
+
# end
|
235
|
+
#
|
236
|
+
# if @stack.size < 1
|
237
|
+
# return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION)
|
238
|
+
# end
|
239
|
+
#
|
240
|
+
# # Note that elsewhere numeric opcodes are limited to
|
241
|
+
# # operands in the range -2**31+1 to 2**31-1, however it is
|
242
|
+
# # legal for opcodes to produce results exceeding that
|
243
|
+
# # range. This limitation is implemented by CScriptNum's
|
244
|
+
# # default 4-byte limit.
|
245
|
+
# #
|
246
|
+
# # If we kept to that limit we'd have a year 2038 problem,
|
247
|
+
# # even though the nLockTime field in transactions
|
248
|
+
# # themselves is uint32 which only becomes meaningless
|
249
|
+
# # after the year 2106.
|
250
|
+
# #
|
251
|
+
# # Thus as a special case we tell CScriptNum to accept up
|
252
|
+
# # to 5-byte bignums, which are good until 2**39-1, well
|
253
|
+
# # beyond the 2**32-1 limit of the nLockTime field itself.
|
254
|
+
# locktime = cast_to_number(@stack.last, max_size: @locktime_max_size)
|
255
|
+
#
|
256
|
+
# # In the rare event that the argument may be < 0 due to
|
257
|
+
# # some arithmetic being done first, you can always use
|
258
|
+
# # 0 MAX CHECKLOCKTIMEVERIFY.
|
259
|
+
# if locktime < 0
|
260
|
+
# return set_error(SCRIPT_ERR_NEGATIVE_LOCKTIME)
|
261
|
+
# end
|
262
|
+
#
|
263
|
+
# # Actually compare the specified lock time with the transaction.
|
264
|
+
# if !signature_checker.check_lock_time(locktime)
|
265
|
+
# return set_error(SCRIPT_ERR_UNSATISFIED_LOCKTIME)
|
266
|
+
# end
|
267
|
+
# end while false
|
251
268
|
|
252
269
|
when OP_NOP1..OP_NOP10
|
253
270
|
if flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS)
|
@@ -567,9 +584,9 @@ module BTC
|
|
567
584
|
when OP_GREATERTHANOREQUAL
|
568
585
|
bn = ScriptNumber.new(boolean: (bn1 >= bn2))
|
569
586
|
when OP_MIN
|
570
|
-
bn =
|
587
|
+
bn = (bn1 < bn2 ? bn1 : bn2)
|
571
588
|
when OP_MAX
|
572
|
-
bn =
|
589
|
+
bn = (bn1 > bn2 ? bn1 : bn2)
|
573
590
|
else
|
574
591
|
raise "Invalid opcode"
|
575
592
|
end
|
@@ -775,7 +792,7 @@ module BTC
|
|
775
792
|
end
|
776
793
|
|
777
794
|
else # unknown opcode
|
778
|
-
return set_error(SCRIPT_ERR_BAD_OPCODE)
|
795
|
+
return set_error(SCRIPT_ERR_BAD_OPCODE, "Unknown opcode 0x#{opcode.to_s(16)}")
|
779
796
|
|
780
797
|
end # case opcode
|
781
798
|
end # within IF scope
|
@@ -792,6 +809,8 @@ module BTC
|
|
792
809
|
end
|
793
810
|
|
794
811
|
return true
|
812
|
+
rescue ScriptNumberError => e
|
813
|
+
return set_error(SCRIPT_ERR_UNKNOWN_ERROR, e.message)
|
795
814
|
end # run_script
|
796
815
|
|
797
816
|
|
@@ -806,6 +825,30 @@ module BTC
|
|
806
825
|
#puts "PUSHED TO STACK: #{@stack.map{|s|s.to_hex}.join(' ')}"
|
807
826
|
end
|
808
827
|
|
828
|
+
# aka CheckMinimalPush(const valtype& data, opcodetype opcode)
|
829
|
+
def check_minimal_push(data, opcode)
|
830
|
+
if data.bytesize == 0
|
831
|
+
# Could have used OP_0.
|
832
|
+
return opcode == OP_0
|
833
|
+
elsif data.bytesize == 1 && data.bytes[0] >= 1 && data.bytes[0] <= 16
|
834
|
+
# Could have used OP_1 .. OP_16.
|
835
|
+
return opcode == OP_1 + (data.bytes[0] - 1)
|
836
|
+
elsif data.bytesize == 1 && data.bytes[0] == 0x81
|
837
|
+
# Could have used OP_1NEGATE.
|
838
|
+
return opcode == OP_1NEGATE
|
839
|
+
elsif data.bytesize <= 75
|
840
|
+
# Could have used a direct push (opcode indicating number of bytes pushed + those bytes).
|
841
|
+
return opcode == data.bytesize
|
842
|
+
elsif data.bytesize <= 255
|
843
|
+
# Could have used OP_PUSHDATA.
|
844
|
+
return opcode == OP_PUSHDATA1
|
845
|
+
elsif (data.bytesize <= 65535)
|
846
|
+
# Could have used OP_PUSHDATA2.
|
847
|
+
return opcode == OP_PUSHDATA2
|
848
|
+
end
|
849
|
+
return true
|
850
|
+
end
|
851
|
+
|
809
852
|
def check_signature_encoding(sig)
|
810
853
|
# Empty signature. Not strictly DER encoded, but allowed to provide a
|
811
854
|
# compact way to provide an invalid signature for use with CHECK(MULTI)SIG
|
@@ -847,7 +890,13 @@ module BTC
|
|
847
890
|
|
848
891
|
# If multiple flags are provided, returns true if any of them are present
|
849
892
|
def flag?(flags)
|
850
|
-
(
|
893
|
+
(all_flags & flags) != 0
|
894
|
+
end
|
895
|
+
|
896
|
+
def all_flags
|
897
|
+
@plugins.inject(@flags) do |f, p|
|
898
|
+
f | p.extra_flags
|
899
|
+
end
|
851
900
|
end
|
852
901
|
|
853
902
|
def cast_to_number(data,
|
@@ -876,6 +925,67 @@ module BTC
|
|
876
925
|
false
|
877
926
|
end
|
878
927
|
|
928
|
+
private
|
929
|
+
|
930
|
+
def plugin_to_handle_scripts(signature_script, output_script)
|
931
|
+
@plugins.each do |plugin|
|
932
|
+
if plugin.should_handle_scripts(interpreter: self, signature_script: signature_script, output_script: output_script)
|
933
|
+
return plugin
|
934
|
+
end
|
935
|
+
end
|
936
|
+
nil
|
937
|
+
end
|
938
|
+
|
939
|
+
def plugin_to_handle_output_script(output_script)
|
940
|
+
@plugins.each do |plugin|
|
941
|
+
if plugin.should_handle_output_script(interpreter: self, output_script: output_script)
|
942
|
+
return plugin
|
943
|
+
end
|
944
|
+
end
|
945
|
+
nil
|
946
|
+
end
|
947
|
+
|
948
|
+
def plugin_to_handle_opcode(opcode)
|
949
|
+
@plugins.each do |plugin|
|
950
|
+
if plugin.should_handle_opcode(interpreter: self, opcode: opcode)
|
951
|
+
return plugin
|
952
|
+
end
|
953
|
+
end
|
954
|
+
nil
|
955
|
+
end
|
956
|
+
|
957
|
+
def did_execute_signature_script(signature_script)
|
958
|
+
@plugins.each do |plugin|
|
959
|
+
if !plugin.did_execute_signature_script(interpreter: self, signature_script: signature_script)
|
960
|
+
return false
|
961
|
+
end
|
962
|
+
end
|
963
|
+
end
|
964
|
+
|
965
|
+
def did_execute_output_script(output_script)
|
966
|
+
@plugins.each do |plugin|
|
967
|
+
if !plugin.did_execute_output_script(interpreter: self, output_script: output_script)
|
968
|
+
return false
|
969
|
+
end
|
970
|
+
end
|
971
|
+
end
|
972
|
+
|
973
|
+
def verify_clean_stack_if_needed
|
974
|
+
# The CLEANSTACK check is only performed after potential P2SH evaluation,
|
975
|
+
# as the non-P2SH evaluation of a P2SH script will obviously not result in
|
976
|
+
# a clean stack (the P2SH inputs remain).
|
977
|
+
if flag?(SCRIPT_VERIFY_CLEANSTACK)
|
978
|
+
# Disallow CLEANSTACK without P2SH, as otherwise a switch CLEANSTACK->P2SH+CLEANSTACK
|
979
|
+
# would be possible, which is not a softfork (and P2SH should be one).
|
980
|
+
if !flag?(SCRIPT_VERIFY_P2SH)
|
981
|
+
raise ArgumentError, "CLEANSTACK without P2SH is disallowed"
|
982
|
+
end
|
983
|
+
if @stack.size != 1
|
984
|
+
return set_error(SCRIPT_ERR_CLEANSTACK, "Stack must be clean (should contain one item 'true')")
|
985
|
+
end
|
986
|
+
end
|
987
|
+
return true
|
988
|
+
end
|
879
989
|
|
880
990
|
end
|
881
991
|
end
|