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 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