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.
- checksums.yaml +4 -4
- data/bitcoinrb.gemspec +1 -0
- data/lib/bitcoin/connection.rb +7 -5
- data/lib/bitcoin/constants.rb +157 -0
- data/lib/bitcoin/key.rb +15 -3
- data/lib/bitcoin/message/base.rb +0 -12
- data/lib/bitcoin/message/handler.rb +27 -14
- data/lib/bitcoin/message/version.rb +1 -1
- data/lib/bitcoin/message.rb +10 -1
- data/lib/bitcoin/opcodes.rb +2 -2
- data/lib/bitcoin/out_point.rb +5 -1
- data/lib/bitcoin/script/script.rb +117 -38
- data/lib/bitcoin/script/script_error.rb +0 -61
- data/lib/bitcoin/script/script_interpreter.rb +164 -154
- data/lib/bitcoin/script/tx_checker.rb +48 -12
- data/lib/bitcoin/secp256k1/native.rb +139 -5
- data/lib/bitcoin/secp256k1/ruby.rb +2 -13
- data/lib/bitcoin/tx.rb +115 -18
- data/lib/bitcoin/tx_in.rb +23 -4
- data/lib/bitcoin/tx_out.rb +1 -1
- data/lib/bitcoin/validation.rb +93 -0
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin.rb +31 -2
- metadata +19 -3
@@ -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
|
-
|
25
|
-
|
26
|
-
|
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
|
-
#
|
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
|
-
|
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
|
-
#
|
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
|
-
|
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
|
-
|
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
|
16
|
-
|
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
|
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]
|
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
|
-
|
110
|
-
|
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
|
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 ==
|
166
|
+
if sig_version == :witness_v0
|
116
167
|
raise ArgumentError, 'amount must be specified.' unless amount
|
117
|
-
sighash_for_witness(input_index,
|
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
|
-
|
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
|
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
|
217
|
+
when SIGHASH_TYPE[:none]
|
145
218
|
outs = ''
|
146
219
|
out_size = Bitcoin.pack_var_int(0)
|
147
|
-
when
|
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 &
|
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,
|
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
|
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
|
252
|
+
when SIGHASH_TYPE[:none]
|
178
253
|
hash_sequence = hash_outputs = "\x00".ljust(32, "\x00")
|
179
254
|
end
|
180
255
|
|
181
|
-
if (hash_type &
|
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,
|
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
|
-
|
11
|
+
# Setting nSequence to this value for every input in a transaction disables nLockTime.
|
12
|
+
SEQUENCE_FINAL = 0xffffffff
|
12
13
|
|
13
|
-
|
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.
|
28
|
-
|
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
|
data/lib/bitcoin/tx_out.rb
CHANGED
@@ -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('
|
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
|
data/lib/bitcoin/version.rb
CHANGED