bitcoinrb 0.1.8 → 0.1.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/bitcoin/ext_key.rb +32 -5
- data/lib/bitcoin/key.rb +33 -9
- data/lib/bitcoin/network/connection.rb +0 -1
- data/lib/bitcoin/network/message_handler.rb +5 -0
- data/lib/bitcoin/psbt.rb +293 -0
- data/lib/bitcoin/script/script.rb +6 -1
- data/lib/bitcoin/script/tx_checker.rb +2 -1
- data/lib/bitcoin/tx.rb +3 -0
- data/lib/bitcoin/tx_in.rb +4 -0
- data/lib/bitcoin/tx_out.rb +4 -0
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin.rb +2 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5f851cfc67b23f2e481679f9c971c1fa55aa236a9c594d1db27934466218437a
|
4
|
+
data.tar.gz: 58a4772dd01dd72ba2a306fcaaa635d993dee788e08fccbb211fc257edc93a5e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 70ab8a795a33371e32d68c49610f521491c3ad9b09bf5dda84b9f21bcc2c8fcf301a3c16d6589587a604cbedd7add37158b1337218a948727617a5fcd00bc149
|
7
|
+
data.tar.gz: 87e4204a69edff47d35e7c0185c48f2c9bc0ac698d11e9f4a1d1c60778cd8ba4fb6c8fc9218bbab2ab3fe219b91ad45385b67b884077185da992b49a1822591a
|
data/lib/bitcoin/ext_key.rb
CHANGED
@@ -22,7 +22,7 @@ module Bitcoin
|
|
22
22
|
l = Bitcoin.hmac_sha512('Bitcoin seed', seed.htb)
|
23
23
|
left = l[0..31].bth.to_i(16)
|
24
24
|
raise 'invalid key' if left >= CURVE_ORDER || left == 0
|
25
|
-
ext_key.key = Bitcoin::Key.new(priv_key: l[0..31].bth)
|
25
|
+
ext_key.key = Bitcoin::Key.new(priv_key: l[0..31].bth, key_type: Bitcoin::Key::TYPES[:compressed])
|
26
26
|
ext_key.chain_code = l[32..-1]
|
27
27
|
ext_key
|
28
28
|
end
|
@@ -106,7 +106,8 @@ module Bitcoin
|
|
106
106
|
raise 'invalid key' if left >= CURVE_ORDER
|
107
107
|
child_priv = (left + key.priv_key.to_i(16)) % CURVE_ORDER
|
108
108
|
raise 'invalid key ' if child_priv >= CURVE_ORDER
|
109
|
-
new_key.key = Bitcoin::Key.new(
|
109
|
+
new_key.key = Bitcoin::Key.new(
|
110
|
+
priv_key: child_priv.to_even_length_hex.rjust(64, '0'), key_type: key_type)
|
110
111
|
new_key.chain_code = l[32..-1]
|
111
112
|
new_key.ver = version
|
112
113
|
new_key
|
@@ -118,6 +119,19 @@ module Bitcoin
|
|
118
119
|
ver ? ver : Bitcoin.chain_params.extended_privkey_version
|
119
120
|
end
|
120
121
|
|
122
|
+
# get key type defined by BIP-178 using version.
|
123
|
+
def key_type
|
124
|
+
v = version
|
125
|
+
case v
|
126
|
+
when Bitcoin.chain_params.bip49_privkey_p2wpkh_p2sh_version
|
127
|
+
Bitcoin::Key::TYPES[:pw2pkh_p2sh]
|
128
|
+
when Bitcoin.chain_params.bip84_privkey_p2wpkh_version
|
129
|
+
Bitcoin::Key::TYPES[:p2wpkh]
|
130
|
+
when Bitcoin.chain_params.extended_privkey_version
|
131
|
+
Bitcoin::Key::TYPES[:compressed]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
121
135
|
def self.parse_from_payload(payload)
|
122
136
|
buf = StringIO.new(payload)
|
123
137
|
ext_key = ExtKey.new
|
@@ -128,7 +142,7 @@ module Bitcoin
|
|
128
142
|
ext_key.number = buf.read(4).unpack('N').first
|
129
143
|
ext_key.chain_code = buf.read(32)
|
130
144
|
buf.read(1) # 0x00
|
131
|
-
ext_key.key = Bitcoin::Key.new(priv_key: buf.read(32).bth)
|
145
|
+
ext_key.key = Bitcoin::Key.new(priv_key: buf.read(32).bth, key_type: Bitcoin::Key::TYPES[:compressed])
|
132
146
|
ext_key
|
133
147
|
end
|
134
148
|
|
@@ -209,7 +223,7 @@ module Bitcoin
|
|
209
223
|
# get key object
|
210
224
|
# @return [Bitcoin::Key]
|
211
225
|
def key
|
212
|
-
Bitcoin::Key.new(pubkey: pubkey)
|
226
|
+
Bitcoin::Key.new(pubkey: pubkey, key_type: key_type)
|
213
227
|
end
|
214
228
|
|
215
229
|
# get key identifier
|
@@ -246,7 +260,7 @@ module Bitcoin
|
|
246
260
|
left = l[0..31].bth.to_i(16)
|
247
261
|
raise 'invalid key' if left >= CURVE_ORDER
|
248
262
|
p1 = Bitcoin::Secp256k1::GROUP.generator.multiply_by_scalar(left)
|
249
|
-
p2 = Bitcoin::Key.new(pubkey: pubkey).to_point
|
263
|
+
p2 = Bitcoin::Key.new(pubkey: pubkey, key_type: key_type).to_point
|
250
264
|
new_key.pubkey = ECDSA::Format::PointOctetString.encode(p1 + p2, compression: true).bth
|
251
265
|
new_key.chain_code = l[32..-1]
|
252
266
|
new_key.ver = version
|
@@ -259,6 +273,19 @@ module Bitcoin
|
|
259
273
|
ver ? ver : Bitcoin.chain_params.extended_pubkey_version
|
260
274
|
end
|
261
275
|
|
276
|
+
# get key type defined by BIP-178 using version.
|
277
|
+
def key_type
|
278
|
+
v = version
|
279
|
+
case v
|
280
|
+
when Bitcoin.chain_params.bip49_pubkey_p2wpkh_p2sh_version
|
281
|
+
Bitcoin::Key::TYPES[:pw2pkh_p2sh]
|
282
|
+
when Bitcoin.chain_params.bip84_pubkey_p2wpkh_version
|
283
|
+
Bitcoin::Key::TYPES[:p2wpkh]
|
284
|
+
when Bitcoin.chain_params.extended_pubkey_version
|
285
|
+
Bitcoin::Key::TYPES[:compressed]
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
262
289
|
def self.parse_from_payload(payload)
|
263
290
|
buf = StringIO.new(payload)
|
264
291
|
ext_pubkey = ExtPubkey.new
|
data/lib/bitcoin/key.rb
CHANGED
@@ -8,14 +8,29 @@ module Bitcoin
|
|
8
8
|
|
9
9
|
attr_accessor :priv_key
|
10
10
|
attr_accessor :pubkey
|
11
|
-
attr_accessor :
|
11
|
+
attr_accessor :key_type
|
12
12
|
attr_reader :secp256k1_module
|
13
13
|
|
14
|
+
TYPES = {uncompressed: 0x00, compressed: 0x01, p2pkh: 0x10, p2wpkh: 0x11, pw2pkh_p2sh: 0x12}
|
15
|
+
|
14
16
|
MIN_PRIV_KEy_MOD_ORDER = 0x01
|
15
17
|
# Order of secp256k1's generator minus 1.
|
16
18
|
MAX_PRIV_KEY_MOD_ORDER = ECDSA::Group::Secp256k1.order - 1
|
17
19
|
|
18
|
-
|
20
|
+
# initialize private key
|
21
|
+
# @param [String] priv_key a private key with hex format.
|
22
|
+
# @param [String] pubkey a public key with hex format.
|
23
|
+
# @param [Integer] key_type a key type which determine address type.
|
24
|
+
# @param [Boolean] compressed [Deprecated] whether public key is compressed.
|
25
|
+
# @return [Bitcoin::Key] a key object.
|
26
|
+
def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true)
|
27
|
+
puts "[Warning] Use key_type parameter instead of compressed. compressed parameter removed in the future." if key_type.nil? && !compressed.nil? && pubkey.nil?
|
28
|
+
if key_type
|
29
|
+
@key_type = key_type
|
30
|
+
compressed = @key_type != TYPES[:uncompressed]
|
31
|
+
else
|
32
|
+
@key_type = compressed ? TYPES[:compressed] : TYPES[:uncompressed]
|
33
|
+
end
|
19
34
|
@secp256k1_module = Bitcoin.secp_impl
|
20
35
|
@priv_key = priv_key
|
21
36
|
if @priv_key
|
@@ -26,13 +41,12 @@ module Bitcoin
|
|
26
41
|
else
|
27
42
|
@pubkey = generate_pubkey(priv_key, compressed: compressed) if priv_key
|
28
43
|
end
|
29
|
-
@compressed = compressed
|
30
44
|
end
|
31
45
|
|
32
46
|
# generate key pair
|
33
|
-
def self.generate
|
47
|
+
def self.generate(key_type = TYPES[:compressed])
|
34
48
|
priv_key, pubkey = Bitcoin.secp_impl.generate_key_pair
|
35
|
-
new(priv_key: priv_key, pubkey: pubkey)
|
49
|
+
new(priv_key: priv_key, pubkey: pubkey, key_type: key_type)
|
36
50
|
end
|
37
51
|
|
38
52
|
# import private key from wif format
|
@@ -47,14 +61,14 @@ module Bitcoin
|
|
47
61
|
raise ArgumentError, 'invalid checksum' unless Bitcoin.calc_checksum(version + data.bth) == checksum
|
48
62
|
key_len = data.bytesize
|
49
63
|
if key_len == 33 && data[-1].unpack('C').first == 1
|
50
|
-
|
64
|
+
key_type = TYPES[:compressed]
|
51
65
|
data = data[0..-2]
|
52
66
|
elsif key_len == 32
|
53
|
-
|
67
|
+
key_type = TYPES[:uncompressed]
|
54
68
|
else
|
55
69
|
raise ArgumentError, 'Wrong number of bytes for a private key, not 32 or 33'
|
56
70
|
end
|
57
|
-
new(priv_key: data.bth,
|
71
|
+
new(priv_key: data.bth, key_type: key_type)
|
58
72
|
end
|
59
73
|
|
60
74
|
# export private key with wif format
|
@@ -93,22 +107,25 @@ module Bitcoin
|
|
93
107
|
end
|
94
108
|
|
95
109
|
# get pay to pubkey hash address
|
110
|
+
# @deprecated
|
96
111
|
def to_p2pkh
|
97
112
|
Bitcoin::Script.to_p2pkh(hash160).addresses.first
|
98
113
|
end
|
99
114
|
|
100
115
|
# get pay to witness pubkey hash address
|
116
|
+
# @deprecated
|
101
117
|
def to_p2wpkh
|
102
118
|
Bitcoin::Script.to_p2wpkh(hash160).addresses.first
|
103
119
|
end
|
104
120
|
|
105
121
|
# get p2wpkh address nested in p2sh.
|
122
|
+
# @deprecated
|
106
123
|
def to_nested_p2wpkh
|
107
124
|
Bitcoin::Script.to_p2wpkh(hash160).to_p2sh.addresses.first
|
108
125
|
end
|
109
126
|
|
110
127
|
def compressed?
|
111
|
-
|
128
|
+
key_type != TYPES[:uncompressed]
|
112
129
|
end
|
113
130
|
|
114
131
|
# generate pubkey ec point
|
@@ -191,6 +208,13 @@ module Bitcoin
|
|
191
208
|
true
|
192
209
|
end
|
193
210
|
|
211
|
+
# fully validate whether this is a valid public key (more expensive than IsValid())
|
212
|
+
def fully_valid_pubkey?
|
213
|
+
return false unless valid_pubkey?
|
214
|
+
point = ECDSA::Format::PointOctetString.decode(pubkey.htb, ECDSA::Group::Secp256k1)
|
215
|
+
ECDSA::Group::Secp256k1.valid_public_key?(point)
|
216
|
+
end
|
217
|
+
|
194
218
|
private
|
195
219
|
|
196
220
|
def self.compare_big_endian(c1, c2)
|
@@ -94,6 +94,8 @@ module Bitcoin
|
|
94
94
|
on_merkle_block(Bitcoin::Message::MerkleBlock.parse_from_payload(payload))
|
95
95
|
when Bitcoin::Message::CmpctBlock::COMMAND
|
96
96
|
on_cmpct_block(Bitcoin::Message::CmpctBlock.parse_from_payload(payload))
|
97
|
+
when Bitcoin::Message::GetData::COMMAND
|
98
|
+
on_get_data(Bitcoin::Message::GetData.parse_from_payload(payload))
|
97
99
|
else
|
98
100
|
logger.warn("unsupported command received. command: #{command}, payload: #{payload.bth}")
|
99
101
|
close("with command #{command}")
|
@@ -230,6 +232,9 @@ module Bitcoin
|
|
230
232
|
logger.info("receive cmpct_block message. #{cmpct_block.build_json}")
|
231
233
|
end
|
232
234
|
|
235
|
+
def on_get_data(get_data)
|
236
|
+
logger.info("receive get data message. #{get_data.build_json}")
|
237
|
+
end
|
233
238
|
end
|
234
239
|
end
|
235
240
|
end
|
data/lib/bitcoin/psbt.rb
ADDED
@@ -0,0 +1,293 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
|
3
|
+
# constants for PSBT
|
4
|
+
PSBT_MAGIC_BYTES = 0x70736274
|
5
|
+
PSBT_TYPES = {tx_or_non_witness: 0x00, redeem_or_witness_utxo: 0x01,
|
6
|
+
witness_or_partial_sig: 0x02, key_path_or_sighash: 0x03, input_num_or_index: 0x04}
|
7
|
+
PSBT_SEPARATOR = 0x00
|
8
|
+
|
9
|
+
# Partially Signed Bitcoin Transaction
|
10
|
+
class PartiallySignedTx
|
11
|
+
|
12
|
+
attr_accessor :tx
|
13
|
+
attr_accessor :redeem_scripts
|
14
|
+
attr_accessor :witness_scripts
|
15
|
+
attr_accessor :partial_inputs
|
16
|
+
attr_accessor :hd_key_paths
|
17
|
+
attr_accessor :num_input
|
18
|
+
attr_accessor :unknowns
|
19
|
+
attr_accessor :use_in_index
|
20
|
+
|
21
|
+
def initialize(tx = nil)
|
22
|
+
@tx = tx
|
23
|
+
@redeem_scripts = {}
|
24
|
+
@witness_scripts = {}
|
25
|
+
@partial_inputs = []
|
26
|
+
@hd_key_paths = {}
|
27
|
+
@unknowns = {}
|
28
|
+
@use_in_index = false
|
29
|
+
end
|
30
|
+
|
31
|
+
# parse Partially Signed Bitcoin Transaction data.
|
32
|
+
# @param [String] payload a Partially Signed Bitcoin Transaction data with binary format.
|
33
|
+
# @return [Bitcoin::PartiallySignedTx]
|
34
|
+
def self.parse_from_payload(payload)
|
35
|
+
buf = StringIO.new(payload)
|
36
|
+
raise ArgumentError, 'Invalid PSBT magic bytes.' unless buf.read(4).unpack('N').first == PSBT_MAGIC_BYTES
|
37
|
+
raise ArgumentError, 'Invalid PSBT separator.' unless buf.read(1).bth.to_i(16) == 0xff
|
38
|
+
partial_tx = self.new
|
39
|
+
is_global = true
|
40
|
+
separators = 0
|
41
|
+
input = PartiallySignedInput.new
|
42
|
+
until buf.eof?
|
43
|
+
key_len = Bitcoin.unpack_var_int_from_io(buf)
|
44
|
+
if key_len == 0
|
45
|
+
puts "use child"
|
46
|
+
is_global = false
|
47
|
+
if separators > 0
|
48
|
+
if partial_tx.use_in_index
|
49
|
+
raise ArgumentError, 'Input indexes being used but an input was provided without an index' unless input.use_in_index
|
50
|
+
(partial_tx.partial_inputs.size...input.index).each{partial_tx.partial_inputs << PartiallySignedInput.new}
|
51
|
+
end
|
52
|
+
partial_tx.partial_inputs << input
|
53
|
+
input = PartiallySignedInput.new(separators)
|
54
|
+
end
|
55
|
+
separators += 1
|
56
|
+
next
|
57
|
+
end
|
58
|
+
|
59
|
+
key_type = buf.read(1).unpack('C').first
|
60
|
+
key = buf.read(key_len - 1)
|
61
|
+
value = buf.read(Bitcoin.unpack_var_int_from_io(buf))
|
62
|
+
puts "key_len = #{key_len}[#{key_type}] key: #{key.nil? ? key : key.bth}, value: #{value.bth}"
|
63
|
+
|
64
|
+
raise ArgumentError, 'Missing null terminator.' if buf.eof? && (key_type != 0 || !key.empty? || !value.empty?)
|
65
|
+
|
66
|
+
case key_type
|
67
|
+
when PSBT_TYPES[:tx_or_non_witness]
|
68
|
+
tx = Bitcoin::Tx.parse_from_payload(value)
|
69
|
+
if is_global
|
70
|
+
partial_tx.tx = tx
|
71
|
+
else
|
72
|
+
raise ArgumentError, 'Provided non witness utxo does not match the required utxo for input' unless partial_tx.tx.in[input.index].out_point.hash == tx.hash
|
73
|
+
input.non_witness_utxo = tx
|
74
|
+
end
|
75
|
+
when PSBT_TYPES[:redeem_or_witness_utxo]
|
76
|
+
if is_global
|
77
|
+
redeem_hash = key
|
78
|
+
raise ArgumentError, 'Size of key was not the expected size for the type redeem script' unless redeem_hash.bytesize == 20
|
79
|
+
raise ArgumentError, 'Provided hash160 does not match the redeemscript\'s hash160' unless Bitcoin.hash160(value.bth) == redeem_hash.bth
|
80
|
+
partial_tx.redeem_scripts[redeem_hash.bth] = value
|
81
|
+
else
|
82
|
+
input.witness_utxo = Bitcoin::TxOut.parse_from_payload(value)
|
83
|
+
end
|
84
|
+
when PSBT_TYPES[:witness_or_partial_sig]
|
85
|
+
if is_global
|
86
|
+
witness_hash = key
|
87
|
+
raise ArgumentError, 'Size of key was not the expected size for the type witness script' unless witness_hash.bytesize == 32
|
88
|
+
raise ArgumentError, 'Provided sha256 does not match the witnessscript\'s sha256' unless Bitcoin.sha256(value) == witness_hash
|
89
|
+
partial_tx.witness_scripts[witness_hash.bth] = value
|
90
|
+
else
|
91
|
+
raise ArgumentError, 'Size of key was not the expected size for the type partial signature pubkey' unless [33, 65].include?(key.bytesize)
|
92
|
+
pubkey = Bitcoin::Key.new(pubkey: key.bth)
|
93
|
+
raise ArgumentError, 'Invalid pubkey' unless pubkey.fully_valid_pubkey?
|
94
|
+
input.partial_sigs[pubkey.pubkey] = value
|
95
|
+
end
|
96
|
+
when PSBT_TYPES[:key_path_or_sighash]
|
97
|
+
if is_global
|
98
|
+
raise ArgumentError, 'Size of key was not the expected size for the type BIP32 keypath' unless [33, 65].include?(key.bytesize)
|
99
|
+
pubkey = Bitcoin::Key.new(pubkey: key.bth)
|
100
|
+
raise ArgumentError, 'Invalid pubkey' unless pubkey.fully_valid_pubkey?
|
101
|
+
key_paths = []
|
102
|
+
key_paths << value[0...4] # fingerprint
|
103
|
+
key_paths += value[4..-1].unpack('N*')
|
104
|
+
partial_tx.hd_key_paths[pubkey.pubkey] = key_paths
|
105
|
+
else
|
106
|
+
input.sighash_type = value.unpack('N').first
|
107
|
+
end
|
108
|
+
when PSBT_TYPES[:input_num_or_index]
|
109
|
+
if is_global
|
110
|
+
partial_tx.num_input = Bitcoin.unpack_var_int(value).first
|
111
|
+
else
|
112
|
+
input.index = Bitcoin.unpack_var_int(value).first
|
113
|
+
input.use_in_index = true
|
114
|
+
partial_tx.use_in_index = true
|
115
|
+
end
|
116
|
+
else
|
117
|
+
if is_global
|
118
|
+
partial_tx.unknowns[([key_type].pack('C') + key).bth] = value
|
119
|
+
else
|
120
|
+
input.unknowns[([key_type].pack('C') + key).bth] = value
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
raise ArgumentError, 'Inputs provided does not match the number of inputs stated.' if (separators - 1) != partial_tx.num_input && partial_tx.use_in_index
|
125
|
+
raise ArgumentError, 'Inputs provided does not match the number of inputs in transaction.' unless partial_tx.tx.in.size == partial_tx.partial_inputs.size
|
126
|
+
partial_tx
|
127
|
+
end
|
128
|
+
|
129
|
+
# generate payload.
|
130
|
+
# @return [String] a payload with binary format.
|
131
|
+
def to_payload
|
132
|
+
payload = PSBT_MAGIC_BYTES.to_even_length_hex.htb << 0xff.to_even_length_hex.htb
|
133
|
+
payload << serialize_to_vector(PSBT_TYPES[:tx_or_non_witness], value: tx.to_payload) if tx
|
134
|
+
|
135
|
+
redeem_scripts.each do |k, v|
|
136
|
+
payload << serialize_to_vector(PSBT_TYPES[:redeem_or_witness_utxo], key: k.htb, value: v)
|
137
|
+
end
|
138
|
+
|
139
|
+
witness_scripts.each do |k, v|
|
140
|
+
payload << serialize_to_vector(PSBT_TYPES[:witness_or_partial_sig], key: k.htb, value: v)
|
141
|
+
end
|
142
|
+
|
143
|
+
hd_key_paths.each do |k, v|
|
144
|
+
value = v.map.with_index{|v, i| i == 0 ? v : [v].pack('N')}.join
|
145
|
+
payload << serialize_to_vector(PSBT_TYPES[:key_path_or_sighash], key: k.htb, value: value)
|
146
|
+
end
|
147
|
+
|
148
|
+
payload << serialize_to_vector(PSBT_TYPES[:input_num_or_index], value: Bitcoin.pack_var_int(num_input)) if num_input
|
149
|
+
|
150
|
+
unknowns.each do |k,v|
|
151
|
+
puts "key = #{k}, v = #{v.bth}"
|
152
|
+
payload << Bitcoin.pack_var_int(k.htb.bytesize) << k.htb << Bitcoin.pack_var_int(v.bytesize) << v
|
153
|
+
end
|
154
|
+
|
155
|
+
payload << PSBT_SEPARATOR.to_even_length_hex.htb
|
156
|
+
|
157
|
+
tx.inputs.each_with_index do|tx_in, index|
|
158
|
+
partial_input = partial_inputs[index]
|
159
|
+
next if use_in_index && !partial_input.use_in_index
|
160
|
+
if tx_in.script_sig.empty? && tx_in.script_witness.empty?
|
161
|
+
if partial_input.non_witness_utxo
|
162
|
+
payload << serialize_to_vector(PSBT_TYPES[:tx_or_non_witness], value: partial_input.non_witness_utxo.to_payload)
|
163
|
+
elsif partial_input.witness_utxo
|
164
|
+
payload << serialize_to_vector(PSBT_TYPES[:redeem_or_witness_utxo], value: partial_input.witness_utxo.to_payload)
|
165
|
+
end
|
166
|
+
partial_input.partial_sigs.each do |k, v|
|
167
|
+
payload << serialize_to_vector(PSBT_TYPES[:witness_or_partial_sig], key: k.htb, value: v)
|
168
|
+
end
|
169
|
+
payload << serialize_to_vector(PSBT_TYPES[:key_path_or_sighash], value: [partial_input.sighash_type].pack('N')) if partial_input.sighash_type
|
170
|
+
payload << serialize_to_vector(PSBT_TYPES[:input_num_or_index], value: Bitcoin.pack_var_int(partial_input.index)) if partial_input.use_in_index
|
171
|
+
partial_input.unknowns.each do |k,v|
|
172
|
+
payload << Bitcoin.pack_var_int(k.htb.bytesize) << k.htb << Bitcoin.pack_var_int(v.bytesize) << v
|
173
|
+
end
|
174
|
+
end
|
175
|
+
payload << PSBT_SEPARATOR.to_even_length_hex.htb
|
176
|
+
end
|
177
|
+
|
178
|
+
payload
|
179
|
+
end
|
180
|
+
|
181
|
+
# combine two PSBTs to create one PSBT.
|
182
|
+
# TODO This feature is experimental.
|
183
|
+
# @param [Bitcoin::PartiallySignedTx] psbt PSBT to be combined which must have same property in PartiallySignedTx.
|
184
|
+
# @return [Bitcoin::PartiallySignedTx] combined object.
|
185
|
+
def combine(psbt)
|
186
|
+
raise ArgumentError, 'The argument psbt must be an instance of PartiallySignedTx.' unless psbt.is_a?(PartiallySignedTx)
|
187
|
+
raise ArgumentError, 'The combined transactions are different.' unless tx == psbt.tx
|
188
|
+
raise ArgumentError, 'The use_in_index are different.' unless use_in_index == psbt.use_in_index
|
189
|
+
raise ArgumentError, 'The partial_inputs size are different.' unless partial_inputs.size == psbt.partial_inputs.size
|
190
|
+
raise ArgumentError, 'The unknowns are different.' unless unknowns == psbt.unknowns
|
191
|
+
combined = PartiallySignedTx.new(tx)
|
192
|
+
combined.redeem_scripts = redeem_scripts.merge(psbt.redeem_scripts)
|
193
|
+
combined.witness_scripts = witness_scripts.merge(psbt.witness_scripts)
|
194
|
+
combined.hd_key_paths = hd_key_paths.merge(psbt.hd_key_paths)
|
195
|
+
combined.witness_scripts = witness_scripts.merge(psbt.witness_scripts)
|
196
|
+
partial_inputs.each_with_index {|i, index|combined.partial_inputs[index] = i.combine(psbt.partial_inputs[index])}
|
197
|
+
combined
|
198
|
+
end
|
199
|
+
|
200
|
+
# transforms a PSBT into a network serialized transaction.
|
201
|
+
# For any inputs which are not complete, the Finalizer will emplace an empty scriptSig in the network serialized transaction.
|
202
|
+
# For any input which has a complete set of signatures,
|
203
|
+
# the Finalizer must attempt to build the complete scriptSig and encode it into the network serialized transaction.
|
204
|
+
# TODO This feature is experimental and support only multisig.
|
205
|
+
# @return [Bitcoin::Tx] finalized Tx.
|
206
|
+
def finalize
|
207
|
+
finalize_tx = tx.dup
|
208
|
+
finalize_tx.inputs.each_with_index do |tx_in, i|
|
209
|
+
partial_input = partial_inputs[i]
|
210
|
+
if partial_input.non_witness_utxo
|
211
|
+
utxo = partial_input.non_witness_utxo.out[tx_in.out_point.index]
|
212
|
+
redeem_script = Bitcoin::Script.parse_from_payload(redeem_scripts[utxo.script_pubkey.chunks[1].pushed_data.bth])
|
213
|
+
tx_in.script_sig << Bitcoin::Opcodes::OP_0
|
214
|
+
redeem_script.chunks[1..-3].each do |pubkey|
|
215
|
+
tx_in.script_sig << partial_input.partial_sigs[pubkey.pushed_data.bth].bth
|
216
|
+
end
|
217
|
+
tx_in.script_sig << redeem_script.to_payload.bth
|
218
|
+
# tx_in.script_sig = Bitcoin::Script.new unless finalize_tx.verify_input_sig(i, utxo.script_pubkey)
|
219
|
+
elsif partial_input.witness_utxo
|
220
|
+
utxo = partial_input.witness_utxo
|
221
|
+
tx_in.script_witness.stack << ''
|
222
|
+
witness_scripts.each {|k, v|
|
223
|
+
redeem_script = Bitcoin::Script.parse_from_payload(v)
|
224
|
+
p2wsh = Bitcoin::Script.to_p2wsh(redeem_script)
|
225
|
+
if p2wsh.to_p2sh == utxo.script_pubkey
|
226
|
+
redeem_script.chunks[1..-3].each do |pubkey|
|
227
|
+
tx_in.script_witness.stack << partial_input.partial_sigs[pubkey.pushed_data.bth]
|
228
|
+
end
|
229
|
+
tx_in.script_witness.stack << v
|
230
|
+
tx_in.script_sig << p2wsh.to_payload.bth
|
231
|
+
break
|
232
|
+
end
|
233
|
+
}
|
234
|
+
unless finalize_tx.verify_input_sig(i, utxo.script_pubkey, amount: utxo.value)
|
235
|
+
tx_in.script_sig = Bitcoin::Script.new
|
236
|
+
tx_in.script_witness = Bitcoin::ScriptWitness.new
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
finalize_tx
|
241
|
+
end
|
242
|
+
|
243
|
+
private
|
244
|
+
|
245
|
+
def serialize_to_vector(key_type, key: nil, value: nil)
|
246
|
+
key_len = key_type.to_even_length_hex.htb.bytesize
|
247
|
+
key_len += key.bytesize if key
|
248
|
+
s = Bitcoin.pack_var_int(key_len) << Bitcoin.pack_var_int(key_type)
|
249
|
+
s << key if key
|
250
|
+
s << Bitcoin.pack_var_int(value.bytesize) << value
|
251
|
+
s
|
252
|
+
end
|
253
|
+
|
254
|
+
end
|
255
|
+
|
256
|
+
class PartiallySignedInput
|
257
|
+
|
258
|
+
attr_accessor :non_witness_utxo # Bitcoin::Tx
|
259
|
+
attr_accessor :witness_utxo # Bitcoin::TxOut
|
260
|
+
attr_accessor :partial_sigs
|
261
|
+
attr_accessor :sighash_type
|
262
|
+
attr_accessor :index
|
263
|
+
attr_accessor :unknowns
|
264
|
+
attr_accessor :use_in_index
|
265
|
+
|
266
|
+
def initialize(index = 0, non_witness_utxo: nil, witness_utxo: nil)
|
267
|
+
@non_witness_utxo = non_witness_utxo
|
268
|
+
@witness_utxo = witness_utxo
|
269
|
+
@partial_sigs = {}
|
270
|
+
@index = index
|
271
|
+
@unknowns = {}
|
272
|
+
@use_in_index = false
|
273
|
+
end
|
274
|
+
|
275
|
+
# combine two PSBTs to create one PSBT.
|
276
|
+
# @param [Bitcoin::PartiallySignedInput] psbi PSBI to be combined which must have same property in PartiallySignedInput.
|
277
|
+
# @return [Bitcoin::PartiallySignedInput] combined object.
|
278
|
+
def combine(psbi)
|
279
|
+
raise ArgumentError, 'The argument psbt must be an instance of PartiallySignedInput.' unless psbi.is_a?(PartiallySignedInput)
|
280
|
+
raise ArgumentError, 'The Partially Signed Input\'s non_witness_utxo are different.' unless non_witness_utxo == psbi.non_witness_utxo
|
281
|
+
raise ArgumentError, 'The Partially Signed Input\'s witness_utxo are different.' unless witness_utxo == psbi.witness_utxo
|
282
|
+
raise ArgumentError, 'The Partially Signed Input\'s sighash_type are different.' unless sighash_type == psbi.sighash_type
|
283
|
+
raise ArgumentError, 'The Partially Signed Input\'s use_in_index are different.' unless use_in_index == psbi.use_in_index
|
284
|
+
combined = PartiallySignedInput.new(index, non_witness_utxo: non_witness_utxo, witness_utxo: witness_utxo)
|
285
|
+
combined.use_in_index = use_in_index
|
286
|
+
combined.unknowns = unknowns.merge(psbi.unknowns)
|
287
|
+
combined.partial_sigs = Hash[partial_sigs.merge(psbi.partial_sigs).sort]
|
288
|
+
combined
|
289
|
+
end
|
290
|
+
|
291
|
+
end
|
292
|
+
|
293
|
+
end
|
@@ -116,6 +116,10 @@ module Bitcoin
|
|
116
116
|
to_payload.bth
|
117
117
|
end
|
118
118
|
|
119
|
+
def empty?
|
120
|
+
chunks.size == 0
|
121
|
+
end
|
122
|
+
|
119
123
|
def addresses
|
120
124
|
return [p2pkh_addr] if p2pkh?
|
121
125
|
return [p2sh_addr] if p2sh?
|
@@ -157,7 +161,7 @@ module Bitcoin
|
|
157
161
|
pubkey_count = Opcodes.opcode_to_small_int(chunks[-2].opcode)
|
158
162
|
sig_count = Opcodes.opcode_to_small_int(chunks[0].opcode)
|
159
163
|
return false unless pubkey_count || sig_count
|
160
|
-
sig_count
|
164
|
+
sig_count <= pubkey_count
|
161
165
|
end
|
162
166
|
|
163
167
|
def op_return?
|
@@ -171,6 +175,7 @@ module Bitcoin
|
|
171
175
|
|
172
176
|
def op_return_data
|
173
177
|
return nil unless op_return?
|
178
|
+
return nil if chunks.size == 1
|
174
179
|
chunks[1].pushed_data
|
175
180
|
end
|
176
181
|
|
@@ -23,7 +23,8 @@ module Bitcoin
|
|
23
23
|
sig = script_sig[0..-2]
|
24
24
|
sighash = tx.sighash_for_input(input_index, script_code, hash_type: hash_type,
|
25
25
|
amount: amount, sig_version: sig_version)
|
26
|
-
|
26
|
+
key_type = pubkey.start_with?('02') || pubkey.start_with?('03') ? Key::TYPES[:compressed] : Key::TYPES[:uncompressed]
|
27
|
+
key = Key.new(pubkey: pubkey, key_type: key_type)
|
27
28
|
key.verify(sig, sighash)
|
28
29
|
end
|
29
30
|
|
data/lib/bitcoin/tx.rb
CHANGED
data/lib/bitcoin/tx_in.rb
CHANGED
data/lib/bitcoin/tx_out.rb
CHANGED
data/lib/bitcoin/version.rb
CHANGED
data/lib/bitcoin.rb
CHANGED
@@ -46,6 +46,8 @@ module Bitcoin
|
|
46
46
|
autoload :Wallet, 'bitcoin/wallet'
|
47
47
|
autoload :BloomFilter, 'bitcoin/bloom_filter'
|
48
48
|
autoload :Payments, 'bitcoin/payments'
|
49
|
+
autoload :PartiallySignedTx, 'bitcoin/psbt'
|
50
|
+
autoload :PartiallySignedInput, 'bitcoin/psbt'
|
49
51
|
|
50
52
|
require_relative 'bitcoin/constants'
|
51
53
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bitcoinrb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- azuchi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-06-
|
11
|
+
date: 2018-06-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ecdsa
|
@@ -365,6 +365,7 @@ files:
|
|
365
365
|
- lib/bitcoin/payments/payment_details.pb.rb
|
366
366
|
- lib/bitcoin/payments/payment_request.pb.rb
|
367
367
|
- lib/bitcoin/payments/x509_certificates.pb.rb
|
368
|
+
- lib/bitcoin/psbt.rb
|
368
369
|
- lib/bitcoin/rpc.rb
|
369
370
|
- lib/bitcoin/rpc/http_server.rb
|
370
371
|
- lib/bitcoin/rpc/request_handler.rb
|