bitcoinrb 0.5.0 → 0.9.0
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/.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
|