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