bitcoinrb 0.1.8 → 0.1.9
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/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
|