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