bitcoinrb 0.5.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +37 -0
- data/.rspec_parallel +2 -0
- data/.ruby-version +1 -1
- data/README.md +11 -1
- data/bitcoinrb.gemspec +7 -6
- data/lib/bitcoin/block_filter.rb +14 -0
- data/lib/bitcoin/chain_params.rb +9 -0
- data/lib/bitcoin/chainparams/signet.yml +39 -0
- data/lib/bitcoin/constants.rb +45 -4
- data/lib/bitcoin/descriptor.rb +1 -1
- data/lib/bitcoin/errors.rb +19 -0
- data/lib/bitcoin/ext/array_ext.rb +22 -0
- data/lib/bitcoin/ext/ecdsa.rb +36 -0
- data/lib/bitcoin/ext.rb +1 -0
- data/lib/bitcoin/ext_key.rb +36 -20
- data/lib/bitcoin/key.rb +85 -28
- data/lib/bitcoin/message/addr_v2.rb +34 -0
- data/lib/bitcoin/message/base.rb +16 -0
- data/lib/bitcoin/message/cfcheckpt.rb +2 -2
- data/lib/bitcoin/message/cfheaders.rb +1 -1
- data/lib/bitcoin/message/cfilter.rb +1 -1
- data/lib/bitcoin/message/fee_filter.rb +1 -1
- data/lib/bitcoin/message/filter_load.rb +3 -3
- data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
- data/lib/bitcoin/message/inventory.rb +1 -1
- data/lib/bitcoin/message/merkle_block.rb +1 -1
- data/lib/bitcoin/message/network_addr.rb +141 -18
- data/lib/bitcoin/message/ping.rb +1 -1
- data/lib/bitcoin/message/pong.rb +1 -1
- data/lib/bitcoin/message/send_addr_v2.rb +13 -0
- data/lib/bitcoin/message/send_cmpct.rb +2 -2
- data/lib/bitcoin/message/tx.rb +1 -1
- data/lib/bitcoin/message.rb +72 -0
- data/lib/bitcoin/message_sign.rb +47 -0
- data/lib/bitcoin/mnemonic.rb +2 -2
- data/lib/bitcoin/network/peer_discovery.rb +1 -3
- data/lib/bitcoin/node/configuration.rb +3 -1
- data/lib/bitcoin/node/spv.rb +8 -0
- data/lib/bitcoin/opcodes.rb +14 -1
- data/lib/bitcoin/payment_code.rb +2 -2
- data/lib/bitcoin/payments/payment.pb.rb +1 -1
- data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
- data/lib/bitcoin/psbt/input.rb +4 -4
- data/lib/bitcoin/psbt/output.rb +1 -1
- data/lib/bitcoin/psbt/tx.rb +14 -5
- data/lib/bitcoin/psbt.rb +8 -0
- data/lib/bitcoin/rpc/bitcoin_core_client.rb +1 -1
- data/lib/bitcoin/rpc/request_handler.rb +3 -3
- data/lib/bitcoin/script/script.rb +80 -30
- data/lib/bitcoin/script/script_error.rb +27 -1
- data/lib/bitcoin/script/script_interpreter.rb +164 -62
- data/lib/bitcoin/script/tx_checker.rb +62 -14
- data/lib/bitcoin/secp256k1/native.rb +184 -17
- data/lib/bitcoin/secp256k1/ruby.rb +108 -21
- data/lib/bitcoin/sighash_generator.rb +157 -0
- data/lib/bitcoin/taproot/leaf_node.rb +23 -0
- data/lib/bitcoin/taproot/simple_builder.rb +155 -0
- data/lib/bitcoin/taproot.rb +45 -0
- data/lib/bitcoin/tx.rb +30 -96
- data/lib/bitcoin/tx_in.rb +1 -1
- data/lib/bitcoin/tx_out.rb +2 -3
- data/lib/bitcoin/util.rb +15 -6
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet/account.rb +1 -1
- data/lib/bitcoin.rb +32 -24
- metadata +58 -18
- data/.travis.yml +0 -12
@@ -0,0 +1,155 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Taproot
|
3
|
+
|
4
|
+
# Utility class to construct Taproot outputs from internal key and script tree.keyPathSpending
|
5
|
+
# SimpleBuilder builds a script tree that places all lock scripts, in the order they are added, as leaf nodes.
|
6
|
+
# It is not possible to specify the depth of the locking script or to insert any intermediate nodes.
|
7
|
+
class SimpleBuilder
|
8
|
+
include Bitcoin::Opcodes
|
9
|
+
|
10
|
+
attr_reader :internal_key # String with hex format
|
11
|
+
attr_reader :branches # List of branch that has two child leaves
|
12
|
+
|
13
|
+
# Initialize builder.
|
14
|
+
# @param [String] internal_key Internal public key with hex format.
|
15
|
+
# @param [Array[Bitcoin::Taproot::LeafNode]] leaves (Optional) Array of leaf nodes for each lock condition.
|
16
|
+
# @raise [Bitcoin::Taproot::Builder] +internal_pubkey+ dose not xonly public key or leaf in +leaves+ does not instance of Bitcoin::Taproot::LeafNode.
|
17
|
+
# @return [Bitcoin::Taproot::SimpleBuilder]
|
18
|
+
def initialize(internal_key, leaves = [])
|
19
|
+
raise Error, 'Internal public key must be 32 bytes' unless internal_key.htb.bytesize == 32
|
20
|
+
raise Error, 'leaf must be Bitcoin::Taproot::LeafNode object' if leaves.find{ |leaf| !leaf.is_a?(Bitcoin::Taproot::LeafNode)}
|
21
|
+
|
22
|
+
@leaves = leaves
|
23
|
+
@branches = leaves.each_slice(2).map.to_a
|
24
|
+
@internal_key = internal_key
|
25
|
+
end
|
26
|
+
|
27
|
+
# Add a leaf node to the end of the current branch.
|
28
|
+
# @param [Bitcoin::Taproot::LeafNode] leaf Leaf node to be added.
|
29
|
+
def add_leaf(leaf)
|
30
|
+
raise Error, 'leaf must be Bitcoin::Taproot::LeafNode object' unless leaf.is_a?(Bitcoin::Taproot::LeafNode)
|
31
|
+
|
32
|
+
if branches.last&.size == 1
|
33
|
+
branches.last << leaf
|
34
|
+
else
|
35
|
+
branches << [leaf]
|
36
|
+
end
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
# Add a pair of leaf nodes as a branch. If there is only one, add a branch with only one child.
|
41
|
+
# @param [Bitcoin::Taproot::LeafNode] leaf1 Leaf node to be added.
|
42
|
+
# @param [Bitcoin::Taproot::LeafNode] leaf2 Leaf node to be added.
|
43
|
+
def add_branch(leaf1, leaf2 = nil)
|
44
|
+
raise Error, 'leaf1 must be Bitcoin::Taproot::LeafNode object' unless leaf1.is_a?(Bitcoin::Taproot::LeafNode)
|
45
|
+
raise Error, 'leaf2 must be Bitcoin::Taproot::LeafNode object' if leaf2 && !leaf2.is_a?(Bitcoin::Taproot::LeafNode)
|
46
|
+
|
47
|
+
branches << (leaf2.nil? ? [leaf1] : [leaf1, leaf2])
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# Build P2TR script.
|
52
|
+
# @return [Bitcoin::Script] P2TR script.
|
53
|
+
def build
|
54
|
+
q = tweak_public_key
|
55
|
+
Bitcoin::Script.new << OP_1 << q.xonly_pubkey
|
56
|
+
end
|
57
|
+
|
58
|
+
# Compute the tweaked public key.
|
59
|
+
# @return [Bitcoin::Key] the tweaked public key
|
60
|
+
def tweak_public_key
|
61
|
+
Taproot.tweak_public_key(Bitcoin::Key.from_xonly_pubkey(internal_key), merkle_root)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Compute the secret key for a tweaked public key.
|
65
|
+
# @param [Bitcoin::Key] key key object contains private key.
|
66
|
+
# @return [Bitcoin::Key] secret key for a tweaked public key
|
67
|
+
def tweak_private_key(key)
|
68
|
+
raise Error, 'Requires private key' unless key.priv_key
|
69
|
+
|
70
|
+
Taproot.tweak_private_key(key, merkle_root)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Generate control block needed to unlock with script-path.
|
74
|
+
# @param [Bitcoin::Taproot::LeafNode] leaf Leaf to use for unlocking.
|
75
|
+
# @return [String] control block with binary format.
|
76
|
+
def control_block(leaf)
|
77
|
+
path = inclusion_proof(leaf)
|
78
|
+
parity = tweak_public_key.to_point.has_even_y? ? 0 : 1
|
79
|
+
[parity + leaf.leaf_ver].pack("C") + internal_key.htb + path.join
|
80
|
+
end
|
81
|
+
|
82
|
+
# Generate inclusion proof for +leaf+.
|
83
|
+
# @param [Bitcoin::Taproot::LeafNode] leaf The leaf node in script tree.
|
84
|
+
# @return [Array[String]] Inclusion proof.
|
85
|
+
# @raise [Bitcoin::Taproot::Error] If the specified +leaf+ does not exist
|
86
|
+
def inclusion_proof(leaf)
|
87
|
+
proofs = []
|
88
|
+
target_branch = branches.find{|b| b.include?(leaf)}
|
89
|
+
raise Error 'Specified leaf does not exist' unless target_branch
|
90
|
+
|
91
|
+
# flatten each branch
|
92
|
+
proofs << hash_value(target_branch.find{|b| b != leaf}) if target_branch.size == 2
|
93
|
+
parent_hash = combine_hash(target_branch)
|
94
|
+
parents = branches.map {|pair| combine_hash(pair)}
|
95
|
+
|
96
|
+
until parents.size == 1
|
97
|
+
parents = parents.each_slice(2).map do |pair|
|
98
|
+
combined = combine_hash(pair)
|
99
|
+
unless pair.size == 1
|
100
|
+
if hash_value(pair[0]) == parent_hash
|
101
|
+
proofs << hash_value(pair[1])
|
102
|
+
parent_hash = combined
|
103
|
+
elsif hash_value(pair[1]) == parent_hash
|
104
|
+
proofs << hash_value(pair[0])
|
105
|
+
parent_hash = combined
|
106
|
+
end
|
107
|
+
end
|
108
|
+
combined
|
109
|
+
end
|
110
|
+
end
|
111
|
+
proofs
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
# Compute tweak from script tree.
|
117
|
+
# @return [String] tweak with binary format.
|
118
|
+
def tweak
|
119
|
+
Taproot.tweak(Bitcoin::Key.from_xonly_pubkey(internal_key), merkle_root)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Calculate merkle root from branches.
|
123
|
+
# @return [String] merkle root with hex format.
|
124
|
+
def merkle_root
|
125
|
+
parents = branches.map {|pair| combine_hash(pair)}
|
126
|
+
if parents.empty?
|
127
|
+
parents = ['']
|
128
|
+
elsif parents.size == 1
|
129
|
+
parents = [combine_hash(parents)]
|
130
|
+
else
|
131
|
+
parents = parents.each_slice(2).map { |pair| combine_hash(pair) } until parents.size == 1
|
132
|
+
end
|
133
|
+
parents.first.bth
|
134
|
+
end
|
135
|
+
|
136
|
+
def combine_hash(pair)
|
137
|
+
if pair.size == 1
|
138
|
+
hash_value(pair[0])
|
139
|
+
else
|
140
|
+
hash1 = hash_value(pair[0])
|
141
|
+
hash2 = hash_value(pair[1])
|
142
|
+
|
143
|
+
# Lexicographically sort a and b's hash, and compute parent hash.
|
144
|
+
payload = hash1.bth < hash2.bth ? hash1 + hash2 : hash2 + hash1
|
145
|
+
Bitcoin.tagged_hash('TapBranch', payload)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def hash_value(leaf_or_branch)
|
150
|
+
leaf_or_branch.is_a?(LeafNode) ? leaf_or_branch.leaf_hash : leaf_or_branch
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Taproot
|
3
|
+
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
autoload :LeafNode, 'bitcoin/taproot/leaf_node'
|
7
|
+
autoload :SimpleBuilder, 'bitcoin/taproot/simple_builder'
|
8
|
+
|
9
|
+
module_function
|
10
|
+
|
11
|
+
# Calculate tweak value from +internal_pubkey+ and +merkle_root+.
|
12
|
+
# @param [Bitcoin::Key] internal_key Internal key with hex format(x-only public key).
|
13
|
+
# @param [String] merkle_root Merkle root value of script tree with hex format.
|
14
|
+
# @return [String] teak value with binary format.
|
15
|
+
def tweak(internal_key, merkle_root)
|
16
|
+
raise Error, 'internal_key must be Bitcoin::Key object.' unless internal_key.is_a?(Bitcoin::Key)
|
17
|
+
|
18
|
+
merkle_root ||= ''
|
19
|
+
t = Bitcoin.tagged_hash('TapTweak', internal_key.xonly_pubkey.htb + merkle_root.htb)
|
20
|
+
raise Error, 'tweak value exceeds the curve order' if t.bti >= ECDSA::Group::Secp256k1.order
|
21
|
+
|
22
|
+
t
|
23
|
+
end
|
24
|
+
|
25
|
+
# Generate tweak public key form +internal_pubkey+ and +merkle_root+.
|
26
|
+
# @param [Bitcoin::Key] internal_key Internal key with hex format(x-only public key).
|
27
|
+
# @param [String] merkle_root Merkle root value of script tree with hex format.
|
28
|
+
# @return [Bitcoin::Key] Tweaked public key.
|
29
|
+
def tweak_public_key(internal_key, merkle_root)
|
30
|
+
t = tweak(internal_key, merkle_root)
|
31
|
+
key = Bitcoin::Key.new(priv_key: t.bth, key_type: Key::TYPES[:compressed])
|
32
|
+
Bitcoin::Key.from_point(key.to_point + internal_key.to_point)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Generate tweak private key
|
36
|
+
#
|
37
|
+
def tweak_private_key(internal_private_key, merkle_root)
|
38
|
+
p = internal_private_key.to_point
|
39
|
+
private_key = p.has_even_y? ? internal_private_key.priv_key.to_i(16) :
|
40
|
+
ECDSA::Group::Secp256k1.order - internal_private_key.priv_key.to_i(16)
|
41
|
+
t = tweak(internal_private_key, merkle_root)
|
42
|
+
Bitcoin::Key.new(priv_key: ((t.bti + private_key) % ECDSA::Group::Secp256k1.order).to_even_length_hex)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/bitcoin/tx.rb
CHANGED
@@ -33,21 +33,21 @@ module Bitcoin
|
|
33
33
|
alias_method :in, :inputs
|
34
34
|
alias_method :out, :outputs
|
35
35
|
|
36
|
-
def self.parse_from_payload(payload, non_witness: false)
|
36
|
+
def self.parse_from_payload(payload, non_witness: false, strict: false)
|
37
37
|
buf = payload.is_a?(String) ? StringIO.new(payload) : payload
|
38
38
|
tx = new
|
39
|
-
tx.version = buf.read(4).
|
39
|
+
tx.version = buf.read(4).unpack1('V')
|
40
40
|
|
41
41
|
in_count = Bitcoin.unpack_var_int_from_io(buf)
|
42
|
-
|
42
|
+
has_witness = false
|
43
43
|
if in_count.zero? && !non_witness
|
44
44
|
tx.marker = 0
|
45
|
-
tx.flag = buf.read(1).
|
45
|
+
tx.flag = buf.read(1).unpack1('c')
|
46
46
|
if tx.flag.zero?
|
47
47
|
buf.pos -= 1
|
48
48
|
else
|
49
49
|
in_count = Bitcoin.unpack_var_int_from_io(buf)
|
50
|
-
|
50
|
+
has_witness = true
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
@@ -60,14 +60,14 @@ module Bitcoin
|
|
60
60
|
tx.outputs << TxOut.parse_from_payload(buf)
|
61
61
|
end
|
62
62
|
|
63
|
-
if
|
63
|
+
if has_witness
|
64
64
|
in_count.times do |i|
|
65
65
|
tx.inputs[i].script_witness = Bitcoin::ScriptWitness.parse_from_payload(buf)
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
-
|
70
|
-
|
69
|
+
raise ArgumentError, 'Transaction has unexpected data.' if strict && (buf.pos + 4) != buf.length
|
70
|
+
tx.lock_time = buf.read(4).unpack1('V')
|
71
71
|
tx
|
72
72
|
end
|
73
73
|
|
@@ -188,22 +188,26 @@ module Bitcoin
|
|
188
188
|
# @param [Integer] input_index input index.
|
189
189
|
# @param [Integer] hash_type signature hash type
|
190
190
|
# @param [Bitcoin::Script] output_script script pubkey or script code. if script pubkey is P2WSH, set witness script to this.
|
191
|
+
# @param [Hash] opts Data required for each sig version (amount and skip_separator_index params can also be set to this parameter)
|
191
192
|
# @param [Integer] amount bitcoin amount locked in input. required for witness input only.
|
192
193
|
# @param [Integer] skip_separator_index If output_script is P2WSH and output_script contains any OP_CODESEPARATOR,
|
193
194
|
# 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.
|
194
|
-
|
195
|
-
|
195
|
+
# @param [Array[Bitcoin::TxOut] prevouts Previous outputs referenced by all Tx inputs, required for taproot.
|
196
|
+
# @return [String] signature hash with binary format.
|
197
|
+
def sighash_for_input(input_index, output_script = nil, opts: {}, hash_type: SIGHASH_TYPE[:all],
|
198
|
+
sig_version: :base, amount: nil, skip_separator_index: 0, prevouts: [])
|
196
199
|
raise ArgumentError, 'input_index must be specified.' unless input_index
|
197
200
|
raise ArgumentError, 'does not exist input corresponding to input_index.' if input_index >= inputs.size
|
198
|
-
raise ArgumentError, 'script_pubkey must be specified.'
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
201
|
+
raise ArgumentError, 'script_pubkey must be specified.' if [:base, :witness_v0].include?(sig_version) && output_script.nil?
|
202
|
+
|
203
|
+
opts[:amount] = amount if amount
|
204
|
+
opts[:skip_separator_index] = skip_separator_index
|
205
|
+
opts[:sig_version] = sig_version
|
206
|
+
opts[:script_code] = output_script
|
207
|
+
opts[:prevouts] = prevouts
|
208
|
+
opts[:last_code_separator_pos] ||= 0xffffffff
|
209
|
+
sig_hash_gen = SigHashGenerator.load(sig_version)
|
210
|
+
sig_hash_gen.generate(self, input_index, hash_type, opts)
|
207
211
|
end
|
208
212
|
|
209
213
|
# verify input signature.
|
@@ -211,7 +215,9 @@ module Bitcoin
|
|
211
215
|
# @param [Bitcoin::Script] script_pubkey the script pubkey for target input.
|
212
216
|
# @param [Integer] amount the amount of bitcoin, require for witness program only.
|
213
217
|
# @param [Array] flags the flags used when execute script interpreter.
|
214
|
-
|
218
|
+
# @param [Array[Bitcoin::TxOut]] prevouts Previous outputs referenced by all Tx inputs, required for taproot.
|
219
|
+
# @return [Boolean] result
|
220
|
+
def verify_input_sig(input_index, script_pubkey, amount: nil, flags: STANDARD_SCRIPT_VERIFY_FLAGS, prevouts: [])
|
215
221
|
script_sig = inputs[input_index].script_sig
|
216
222
|
has_witness = inputs[input_index].has_witness?
|
217
223
|
|
@@ -222,7 +228,7 @@ module Bitcoin
|
|
222
228
|
end
|
223
229
|
|
224
230
|
if has_witness
|
225
|
-
verify_input_sig_for_witness(input_index, script_pubkey, amount, flags)
|
231
|
+
verify_input_sig_for_witness(input_index, script_pubkey, amount, flags, prevouts)
|
226
232
|
else
|
227
233
|
verify_input_sig_for_legacy(input_index, script_pubkey, flags)
|
228
234
|
end
|
@@ -245,73 +251,6 @@ module Bitcoin
|
|
245
251
|
|
246
252
|
private
|
247
253
|
|
248
|
-
# generate sighash with legacy format
|
249
|
-
def sighash_for_legacy(index, script_code, hash_type)
|
250
|
-
ins = inputs.map.with_index do |i, idx|
|
251
|
-
if idx == index
|
252
|
-
i.to_payload(script_code.delete_opcode(Bitcoin::Opcodes::OP_CODESEPARATOR))
|
253
|
-
else
|
254
|
-
case hash_type & 0x1f
|
255
|
-
when SIGHASH_TYPE[:none], SIGHASH_TYPE[:single]
|
256
|
-
i.to_payload(Bitcoin::Script.new, 0)
|
257
|
-
else
|
258
|
-
i.to_payload(Bitcoin::Script.new)
|
259
|
-
end
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
outs = outputs.map(&:to_payload)
|
264
|
-
out_size = Bitcoin.pack_var_int(outputs.size)
|
265
|
-
|
266
|
-
case hash_type & 0x1f
|
267
|
-
when SIGHASH_TYPE[:none]
|
268
|
-
outs = ''
|
269
|
-
out_size = Bitcoin.pack_var_int(0)
|
270
|
-
when SIGHASH_TYPE[:single]
|
271
|
-
return "\x01".ljust(32, "\x00") if index >= outputs.size
|
272
|
-
outs = outputs[0...(index + 1)].map.with_index { |o, idx| (idx == index) ? o.to_payload : o.to_empty_payload }.join
|
273
|
-
out_size = Bitcoin.pack_var_int(index + 1)
|
274
|
-
end
|
275
|
-
|
276
|
-
if hash_type & SIGHASH_TYPE[:anyonecanpay] != 0
|
277
|
-
ins = [ins[index]]
|
278
|
-
end
|
279
|
-
|
280
|
-
buf = [[version].pack('V'), Bitcoin.pack_var_int(ins.size),
|
281
|
-
ins, out_size, outs, [lock_time, hash_type].pack('VV')].join
|
282
|
-
|
283
|
-
Bitcoin.double_sha256(buf)
|
284
|
-
end
|
285
|
-
|
286
|
-
# generate sighash with BIP-143 format
|
287
|
-
# https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
|
288
|
-
def sighash_for_witness(index, script_pubkey_or_script_code, hash_type, amount, skip_separator_index)
|
289
|
-
hash_prevouts = Bitcoin.double_sha256(inputs.map{|i|i.out_point.to_payload}.join)
|
290
|
-
hash_sequence = Bitcoin.double_sha256(inputs.map{|i|[i.sequence].pack('V')}.join)
|
291
|
-
outpoint = inputs[index].out_point.to_payload
|
292
|
-
amount = [amount].pack('Q')
|
293
|
-
nsequence = [inputs[index].sequence].pack('V')
|
294
|
-
hash_outputs = Bitcoin.double_sha256(outputs.map{|o|o.to_payload}.join)
|
295
|
-
|
296
|
-
script_code = script_pubkey_or_script_code.to_script_code(skip_separator_index)
|
297
|
-
|
298
|
-
case (hash_type & 0x1f)
|
299
|
-
when SIGHASH_TYPE[:single]
|
300
|
-
hash_outputs = index >= outputs.size ? "\x00".ljust(32, "\x00") : Bitcoin.double_sha256(outputs[index].to_payload)
|
301
|
-
hash_sequence = "\x00".ljust(32, "\x00")
|
302
|
-
when SIGHASH_TYPE[:none]
|
303
|
-
hash_sequence = hash_outputs = "\x00".ljust(32, "\x00")
|
304
|
-
end
|
305
|
-
|
306
|
-
if (hash_type & SIGHASH_TYPE[:anyonecanpay]) != 0
|
307
|
-
hash_prevouts = hash_sequence ="\x00".ljust(32, "\x00")
|
308
|
-
end
|
309
|
-
|
310
|
-
buf = [ [version].pack('V'), hash_prevouts, hash_sequence, outpoint,
|
311
|
-
script_code ,amount, nsequence, hash_outputs, [@lock_time, hash_type].pack('VV')].join
|
312
|
-
Bitcoin.double_sha256(buf)
|
313
|
-
end
|
314
|
-
|
315
254
|
# verify input signature for legacy tx.
|
316
255
|
def verify_input_sig_for_legacy(input_index, script_pubkey, flags)
|
317
256
|
script_sig = inputs[input_index].script_sig
|
@@ -322,16 +261,11 @@ module Bitcoin
|
|
322
261
|
end
|
323
262
|
|
324
263
|
# verify input signature for witness tx.
|
325
|
-
def verify_input_sig_for_witness(input_index, script_pubkey, amount, flags)
|
326
|
-
|
327
|
-
flags |= SCRIPT_VERIFY_WITNESS_PUBKEYTYPE
|
328
|
-
checker = Bitcoin::TxChecker.new(tx: self, input_index: input_index, amount: amount)
|
264
|
+
def verify_input_sig_for_witness(input_index, script_pubkey, amount, flags, prevouts)
|
265
|
+
checker = Bitcoin::TxChecker.new(tx: self, input_index: input_index, amount: amount, prevouts: prevouts)
|
329
266
|
interpreter = Bitcoin::ScriptInterpreter.new(checker: checker, flags: flags)
|
330
267
|
i = inputs[input_index]
|
331
|
-
|
332
|
-
script_sig = i.script_sig
|
333
|
-
witness = i.script_witness
|
334
|
-
interpreter.verify_script(script_sig, script_pubkey, witness)
|
268
|
+
interpreter.verify_script(i.script_sig, script_pubkey, i.script_witness)
|
335
269
|
end
|
336
270
|
|
337
271
|
end
|
data/lib/bitcoin/tx_in.rb
CHANGED
data/lib/bitcoin/tx_out.rb
CHANGED
@@ -18,14 +18,13 @@ module Bitcoin
|
|
18
18
|
|
19
19
|
def self.parse_from_payload(payload)
|
20
20
|
buf = payload.is_a?(String) ? StringIO.new(payload) : payload
|
21
|
-
value = buf.read(8).
|
21
|
+
value = buf.read(8).unpack1('q')
|
22
22
|
script_size = Bitcoin.unpack_var_int_from_io(buf)
|
23
23
|
new(value: value, script_pubkey: Script.parse_from_payload(buf.read(script_size)))
|
24
24
|
end
|
25
25
|
|
26
26
|
def to_payload
|
27
|
-
|
28
|
-
[value].pack('Q') << Bitcoin.pack_var_int(s.length) << s
|
27
|
+
[value].pack('Q') << script_pubkey.to_payload(true)
|
29
28
|
end
|
30
29
|
|
31
30
|
def to_empty_payload
|
data/lib/bitcoin/util.rb
CHANGED
@@ -33,7 +33,7 @@ module Bitcoin
|
|
33
33
|
|
34
34
|
# @return an integer for a valid payload, otherwise nil
|
35
35
|
def unpack_var_int(payload)
|
36
|
-
case payload.
|
36
|
+
case payload.unpack1('C')
|
37
37
|
when 0xfd
|
38
38
|
payload.unpack('xva*')
|
39
39
|
when 0xfe
|
@@ -47,14 +47,14 @@ module Bitcoin
|
|
47
47
|
|
48
48
|
# @return an integer for a valid payload, otherwise nil
|
49
49
|
def unpack_var_int_from_io(buf)
|
50
|
-
uchar = buf.read(1)&.
|
50
|
+
uchar = buf.read(1)&.unpack1('C')
|
51
51
|
case uchar
|
52
52
|
when 0xfd
|
53
|
-
buf.read(2)&.
|
53
|
+
buf.read(2)&.unpack1('v')
|
54
54
|
when 0xfe
|
55
|
-
buf.read(4)&.
|
55
|
+
buf.read(4)&.unpack1('V')
|
56
56
|
when 0xff
|
57
|
-
buf.read(8)&.
|
57
|
+
buf.read(8)&.unpack1('Q')
|
58
58
|
else
|
59
59
|
uchar
|
60
60
|
end
|
@@ -79,7 +79,7 @@ module Bitcoin
|
|
79
79
|
|
80
80
|
# byte convert to the sequence of bits packed eight in a byte with the least significant bit first.
|
81
81
|
def byte_to_bit(byte)
|
82
|
-
byte.
|
82
|
+
byte.unpack1('b*')
|
83
83
|
end
|
84
84
|
|
85
85
|
# padding zero to the left of binary string until bytesize.
|
@@ -96,6 +96,15 @@ module Bitcoin
|
|
96
96
|
Digest::RMD160.hexdigest(Digest::SHA256.digest(hex.htb))
|
97
97
|
end
|
98
98
|
|
99
|
+
# Generate tagged hash value.
|
100
|
+
# @param [String] tag tag value.
|
101
|
+
# @param [String] msg the message to be hashed.
|
102
|
+
# @return [String] the hash value with binary format.
|
103
|
+
def tagged_hash(tag, msg)
|
104
|
+
tag_hash = Digest::SHA256.digest(tag)
|
105
|
+
Digest::SHA256.digest(tag_hash + tag_hash + msg)
|
106
|
+
end
|
107
|
+
|
99
108
|
# encode Base58 check address.
|
100
109
|
# @param [String] hex the address payload.
|
101
110
|
# @param [String] addr_version the address version for P2PKH and P2SH.
|
data/lib/bitcoin/version.rb
CHANGED
@@ -43,7 +43,7 @@ module Bitcoin
|
|
43
43
|
|
44
44
|
def to_payload
|
45
45
|
payload = account_key.to_payload
|
46
|
-
payload << Bitcoin.pack_var_string(name.
|
46
|
+
payload << Bitcoin.pack_var_string(name.unpack1('H*').htb)
|
47
47
|
payload << [purpose, index, receive_depth, change_depth, lookahead].pack('I*')
|
48
48
|
payload
|
49
49
|
end
|
data/lib/bitcoin.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
require 'bitcoin/version'
|
5
5
|
require 'eventmachine'
|
6
|
-
require '
|
6
|
+
require 'schnorr'
|
7
7
|
require 'securerandom'
|
8
8
|
require 'json'
|
9
9
|
require 'bech32'
|
@@ -58,8 +58,13 @@ module Bitcoin
|
|
58
58
|
autoload :Aezeed, 'bitcoin/aezeed'
|
59
59
|
autoload :PaymentCode, 'bitcoin/payment_code'
|
60
60
|
autoload :BIP85Entropy, 'bitcoin/bip85_entropy'
|
61
|
+
autoload :Errors, 'bitcoin/errors'
|
62
|
+
autoload :SigHashGenerator, 'bitcoin/sighash_generator'
|
63
|
+
autoload :MessageSign, 'bitcoin/message_sign'
|
64
|
+
autoload :Taproot, 'bitcoin/taproot'
|
61
65
|
|
62
66
|
require_relative 'bitcoin/constants'
|
67
|
+
require_relative 'bitcoin/ext/ecdsa'
|
63
68
|
|
64
69
|
extend Util
|
65
70
|
|
@@ -67,7 +72,7 @@ module Bitcoin
|
|
67
72
|
|
68
73
|
# set bitcoin network chain params
|
69
74
|
def self.chain_params=(name)
|
70
|
-
raise "chain params for #{name} is not defined." unless %i(mainnet testnet regtest).include?(name.to_sym)
|
75
|
+
raise "chain params for #{name} is not defined." unless %i(mainnet testnet regtest signet).include?(name.to_sym)
|
71
76
|
@current_chain = nil
|
72
77
|
@chain_param = name.to_sym
|
73
78
|
end
|
@@ -82,6 +87,8 @@ module Bitcoin
|
|
82
87
|
@current_chain = Bitcoin::ChainParams.testnet
|
83
88
|
when :regtest
|
84
89
|
@current_chain = Bitcoin::ChainParams.regtest
|
90
|
+
when :signet
|
91
|
+
@current_chain = Bitcoin::ChainParams.signet
|
85
92
|
end
|
86
93
|
@current_chain
|
87
94
|
end
|
@@ -108,7 +115,7 @@ module Bitcoin
|
|
108
115
|
class ::String
|
109
116
|
# binary convert to hex string
|
110
117
|
def bth
|
111
|
-
|
118
|
+
unpack1('H*')
|
112
119
|
end
|
113
120
|
|
114
121
|
# hex string convert to binary
|
@@ -128,14 +135,7 @@ module Bitcoin
|
|
128
135
|
|
129
136
|
# get opcode
|
130
137
|
def opcode
|
131
|
-
|
132
|
-
when Encoding::ASCII_8BIT
|
133
|
-
each_byte.next
|
134
|
-
when Encoding::US_ASCII
|
135
|
-
ord
|
136
|
-
else
|
137
|
-
to_i
|
138
|
-
end
|
138
|
+
force_encoding(Encoding::ASCII_8BIT).ord
|
139
139
|
end
|
140
140
|
|
141
141
|
def opcode?
|
@@ -165,6 +165,27 @@ module Bitcoin
|
|
165
165
|
self[offset..-1]
|
166
166
|
end
|
167
167
|
|
168
|
+
def valid_pushdata_length?
|
169
|
+
buf = StringIO.new(self)
|
170
|
+
opcode = buf.read(1).ord
|
171
|
+
offset = 1
|
172
|
+
return false if buf.eof?
|
173
|
+
len = case opcode
|
174
|
+
when Bitcoin::Opcodes::OP_PUSHDATA1
|
175
|
+
offset += 1
|
176
|
+
buf.read(1).unpack1('C')
|
177
|
+
when Bitcoin::Opcodes::OP_PUSHDATA2
|
178
|
+
offset += 2
|
179
|
+
buf.read(2).unpack1('v')
|
180
|
+
when Bitcoin::Opcodes::OP_PUSHDATA4
|
181
|
+
offset += 4
|
182
|
+
buf.read(4).unpack1('V')
|
183
|
+
else
|
184
|
+
opcode
|
185
|
+
end
|
186
|
+
self.bytesize == len + offset
|
187
|
+
end
|
188
|
+
|
168
189
|
# whether value is hex or not hex
|
169
190
|
# @return [Boolean] return true if data is hex
|
170
191
|
def valid_hex?
|
@@ -219,17 +240,4 @@ module Bitcoin
|
|
219
240
|
end
|
220
241
|
end
|
221
242
|
|
222
|
-
class ::ECDSA::Signature
|
223
|
-
# convert signature to der string.
|
224
|
-
def to_der
|
225
|
-
ECDSA::Format::SignatureDerString.encode(self)
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
|
-
class ::ECDSA::Point
|
230
|
-
def to_hex(compression = true)
|
231
|
-
ECDSA::Format::PointOctetString.encode(self, compression: compression).bth
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
243
|
end
|