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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 369e8ba42e13931c345e3ebf13023a0b45433d647ea42849028557bfb9036aad
4
- data.tar.gz: 460030a26c3cd367706ebb374efda4ad6c7acc3994daa61fd261195076838079
3
+ metadata.gz: 5f851cfc67b23f2e481679f9c971c1fa55aa236a9c594d1db27934466218437a
4
+ data.tar.gz: 58a4772dd01dd72ba2a306fcaaa635d993dee788e08fccbb211fc257edc93a5e
5
5
  SHA512:
6
- metadata.gz: c741a2415026b02e1760fcd47586fbb8df7219502fbf4223bc6d560b313e765bc916871a4ed87d98f343c48e25f72d9bf49910df17073490a2a96db63304ef43
7
- data.tar.gz: 13b72574c4413556d9522bd3825ab818beadf956673f5a3c1ff0d027e736d126176825a7a04bdc4be3d333b96aedb9e9580339a8219937844bd5fd0b9b9d3ba2
6
+ metadata.gz: 70ab8a795a33371e32d68c49610f521491c3ad9b09bf5dda84b9f21bcc2c8fcf301a3c16d6589587a604cbedd7add37158b1337218a948727617a5fcd00bc149
7
+ data.tar.gz: 87e4204a69edff47d35e7c0185c48f2c9bc0ac698d11e9f4a1d1c60778cd8ba4fb6c8fc9218bbab2ab3fe219b91ad45385b67b884077185da992b49a1822591a
@@ -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(priv_key: child_priv.to_even_length_hex.rjust(64, '0'))
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 :compressed
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
- def initialize(priv_key: nil, pubkey: nil, compressed: true)
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
- compressed = true
64
+ key_type = TYPES[:compressed]
51
65
  data = data[0..-2]
52
66
  elsif key_len == 32
53
- compressed = false
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, compressed: compressed)
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
- @compressed
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)
@@ -50,7 +50,6 @@ module Bitcoin
50
50
  def close(msg = '')
51
51
  logger.info "close connection with #{addr}. #{msg}"
52
52
  close_connection_after_writing
53
- EM.stop
54
53
  end
55
54
 
56
55
  def handle_error(e)
@@ -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
@@ -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 < pubkey_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
- key = Key.new(pubkey: pubkey)
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
@@ -28,6 +28,9 @@ module Bitcoin
28
28
  @lock_time = 0
29
29
  end
30
30
 
31
+ alias_method :in, :inputs
32
+ alias_method :out, :outputs
33
+
31
34
  def self.parse_from_payload(payload)
32
35
  buf = payload.is_a?(String) ? StringIO.new(payload) : payload
33
36
  tx = new
data/lib/bitcoin/tx_in.rb CHANGED
@@ -71,6 +71,10 @@ module Bitcoin
71
71
  h
72
72
  end
73
73
 
74
+ def ==(other)
75
+ to_payload == other.to_payload
76
+ end
77
+
74
78
  end
75
79
 
76
80
  end
@@ -41,6 +41,10 @@ module Bitcoin
41
41
  {value: value_to_btc, script_pubkey: script_pubkey.to_h}
42
42
  end
43
43
 
44
+ def ==(other)
45
+ to_payload == other.to_payload
46
+ end
47
+
44
48
  end
45
49
 
46
50
  end
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "0.1.8"
2
+ VERSION = "0.1.9"
3
3
  end
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.8
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-08 00:00:00.000000000 Z
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