bitcoinrb 0.1.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f851cfc67b23f2e481679f9c971c1fa55aa236a9c594d1db27934466218437a
4
- data.tar.gz: 58a4772dd01dd72ba2a306fcaaa635d993dee788e08fccbb211fc257edc93a5e
3
+ metadata.gz: 3bff3d97122521e4859ca7dc471eeda350a5156d763731384c1ed8c3e54b7117
4
+ data.tar.gz: 95a8ef84f38ced9e9f07279eefac81e774a923c0d048593783c4c4c2d5dda49f
5
5
  SHA512:
6
- metadata.gz: 70ab8a795a33371e32d68c49610f521491c3ad9b09bf5dda84b9f21bcc2c8fcf301a3c16d6589587a604cbedd7add37158b1337218a948727617a5fcd00bc149
7
- data.tar.gz: 87e4204a69edff47d35e7c0185c48f2c9bc0ac698d11e9f4a1d1c60778cd8ba4fb6c8fc9218bbab2ab3fe219b91ad45385b67b884077185da992b49a1822591a
6
+ metadata.gz: 2c493349ad0cb5ac702b88a080cb5880407b5b37fc08171d450454e977874e056fcd21b5022c7aee5ffc4f40f43bfd8f63eabdec2e4ba5c6d5655a950da74cea
7
+ data.tar.gz: 84d4d0e3c1b93ea90af3414223b5c83a5753d66bc174a101b4e10388d74ba0077431bedc67ab670ada33226c5a73339973afeda87fda97d1877983f318cf5e29
data/lib/bitcoin/key.rb CHANGED
@@ -6,6 +6,11 @@ module Bitcoin
6
6
  # bitcoin key class
7
7
  class Key
8
8
 
9
+ PUBLIC_KEY_SIZE = 65
10
+ COMPRESSED_PUBLIC_KEY_SIZE = 33
11
+ SIGNATURE_SIZE = 72
12
+ COMPACT_SIGNATURE_SIZE = 65
13
+
9
14
  attr_accessor :priv_key
10
15
  attr_accessor :pubkey
11
16
  attr_accessor :key_type
@@ -60,7 +65,7 @@ module Bitcoin
60
65
  raise ArgumentError, 'invalid version' unless version == Bitcoin.chain_params.privkey_version
61
66
  raise ArgumentError, 'invalid checksum' unless Bitcoin.calc_checksum(version + data.bth) == checksum
62
67
  key_len = data.bytesize
63
- if key_len == 33 && data[-1].unpack('C').first == 1
68
+ if key_len == COMPRESSED_PUBLIC_KEY_SIZE && data[-1].unpack('C').first == 1
64
69
  key_type = TYPES[:compressed]
65
70
  data = data[0..-2]
66
71
  elsif key_len == 32
@@ -139,12 +144,12 @@ module Bitcoin
139
144
  # check +pubkey+ (hex) is compress or uncompress pubkey.
140
145
  def self.compress_or_uncompress_pubkey?(pubkey)
141
146
  p = pubkey.htb
142
- return false if p.bytesize < 33
147
+ return false if p.bytesize < COMPRESSED_PUBLIC_KEY_SIZE
143
148
  case p[0]
144
149
  when "\x04"
145
- return false unless p.bytesize == 65
150
+ return false unless p.bytesize == PUBLIC_KEY_SIZE
146
151
  when "\x02", "\x03"
147
- return false unless p.bytesize == 33
152
+ return false unless p.bytesize == COMPRESSED_PUBLIC_KEY_SIZE
148
153
  else
149
154
  return false
150
155
  end
@@ -154,7 +159,7 @@ module Bitcoin
154
159
  # check +pubkey+ (hex) is compress pubkey.
155
160
  def self.compress_pubkey?(pubkey)
156
161
  p = pubkey.htb
157
- p.bytesize == 33 && ["\x02", "\x03"].include?(p[0])
162
+ p.bytesize == COMPRESSED_PUBLIC_KEY_SIZE && ["\x02", "\x03"].include?(p[0])
158
163
  end
159
164
 
160
165
  # check +sig+ is low.
@@ -0,0 +1,37 @@
1
+ module Bitcoin
2
+ module PSBT
3
+
4
+ # HD Key path data structure.
5
+ # see https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#Specification
6
+ class HDKeyPath
7
+
8
+ attr_reader :pubkey
9
+ attr_accessor :fingerprint
10
+ attr_reader :path
11
+
12
+ def initialize(pubkey, fingerprint: nil, path: [])
13
+ @pubkey = pubkey
14
+ @fingerprint = fingerprint
15
+ @path = path
16
+ end
17
+
18
+ # parse hd key path from payload.
19
+ # @param [String] pubkey a public key with binary format.
20
+ # @param [String] payload hd key path value with binary format.
21
+ # @return [Bitcoin::PSBT::HDKeyPath]
22
+ def self.parse_from_payload(pubkey, payload)
23
+ raise 'Size of key was not the expected size for the type BIP32 keypath' unless [Bitcoin::Key::PUBLIC_KEY_SIZE, Bitcoin::Key::COMPRESSED_PUBLIC_KEY_SIZE].include?(pubkey.bytesize)
24
+ pubkey = Bitcoin::Key.new(pubkey: pubkey.bth)
25
+ raise 'Invalid pubkey' unless pubkey.fully_valid_pubkey?
26
+ self.new(pubkey.pubkey, fingerprint: payload[0...4].bth, path: payload[4..-1].unpack('I*'))
27
+ end
28
+
29
+ # generate payload which consist of pubkey and fingerprint, hd key path payload.
30
+ # @return [String] a payload
31
+ def to_payload(type = PSBT_IN_TYPES[:bip32_derivation])
32
+ PSBT.serialize_to_vector(type, key: pubkey.htb, value: fingerprint.htb + path.pack('I*'))
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,172 @@
1
+ module Bitcoin
2
+
3
+ module PSBT
4
+
5
+ # Class for PSBTs which contain per-input information
6
+ class Input
7
+
8
+ attr_accessor :non_witness_utxo # Bitcoin::Tx
9
+ attr_accessor :witness_utxo # Bitcoin::TxOut
10
+ attr_accessor :redeem_script
11
+ attr_accessor :witness_script
12
+ attr_accessor :final_script_sig
13
+ attr_accessor :final_script_witness
14
+ attr_accessor :hd_key_paths
15
+ attr_accessor :partial_sigs
16
+ attr_accessor :sighash_type
17
+ attr_accessor :unknowns
18
+
19
+ def initialize(non_witness_utxo: nil, witness_utxo: nil)
20
+ @non_witness_utxo = non_witness_utxo
21
+ @witness_utxo = witness_utxo
22
+ @partial_sigs = {}
23
+ @hd_key_paths = {}
24
+ @unknowns = {}
25
+ end
26
+
27
+ # parse PSBT input data form buffer.
28
+ # @param [StringIO] buf psbt buffer.
29
+ # @return [Bitcoin::PSBTInput] psbt input.
30
+ def self.parse_from_buf(buf)
31
+ input = self.new
32
+ until buf.eof?
33
+ key_len = Bitcoin.unpack_var_int_from_io(buf)
34
+ break if key_len == 0
35
+ key_type = buf.read(1).unpack('C').first
36
+ key = buf.read(key_len - 1)
37
+ value = buf.read(Bitcoin.unpack_var_int_from_io(buf))
38
+
39
+ case key_type
40
+ when PSBT_IN_TYPES[:non_witness_utxo]
41
+ raise ArgumentError, 'Duplicate Key, input non-witness utxo already provided.' if input.non_witness_utxo
42
+ input.non_witness_utxo = Bitcoin::Tx.parse_from_payload(value)
43
+ when PSBT_IN_TYPES[:witness_utxo]
44
+ raise ArgumentError, 'Duplicate Key, input witness utxo already provided.' if input.witness_utxo
45
+ input.witness_utxo = Bitcoin::TxOut.parse_from_payload(value)
46
+ when PSBT_IN_TYPES[:partial_sig]
47
+ if key.size != Bitcoin::Key::PUBLIC_KEY_SIZE && key.size != Bitcoin::Key::COMPRESSED_PUBLIC_KEY_SIZE
48
+ raise ArgumentError, 'Size of key was not the expected size for the type partial signature pubkey.'
49
+ end
50
+ pubkey = Bitcoin::Key.new(pubkey: key.bth)
51
+ raise ArgumentError, 'Invalid pubkey.' unless pubkey.fully_valid_pubkey?
52
+ raise ArgumentError, 'Duplicate Key, input partial signature for pubkey already provided.' if input.partial_sigs[pubkey.pubkey]
53
+ input.partial_sigs[pubkey.pubkey] = value
54
+ when PSBT_IN_TYPES[:sighash]
55
+ raise ArgumentError 'Duplicate Key, input sighash type already provided.' if input.sighash_type
56
+ input.sighash_type = value.unpack('I').first
57
+ when PSBT_IN_TYPES[:redeem_script]
58
+ raise ArgumentError, 'Duplicate Key, input redeemScript already provided.' if input.redeem_script
59
+ input.redeem_script = Bitcoin::Script.parse_from_payload(value)
60
+ when PSBT_IN_TYPES[:witness_script]
61
+ raise ArgumentError, 'Duplicate Key, input witnessScript already provided.' if input.witness_script
62
+ input.witness_script = Bitcoin::Script.parse_from_payload(value)
63
+ when PSBT_IN_TYPES[:bip32_derivation]
64
+ raise ArgumentError, 'Duplicate Key, pubkey derivation path already provided.' if input.hd_key_paths[key.bth]
65
+ input.hd_key_paths[key.bth] = Bitcoin::PSBT::HDKeyPath.parse_from_payload(key, value)
66
+ when PSBT_IN_TYPES[:script_sig]
67
+ raise ArgumentError, 'Duplicate Key, input final scriptSig already provided.' if input.final_script_sig
68
+ input.final_script_sig = Bitcoin::Script.parse_from_payload(value)
69
+ when PSBT_IN_TYPES[:script_witness]
70
+ raise ArgumentError, 'Duplicate Key, input final scriptWitness already provided.' if input.final_script_witness
71
+ input.final_script_witness = Bitcoin::ScriptWitness.parse_from_payload(value)
72
+ else
73
+ unknown_key = ([key_type].pack('C') + key).bth
74
+ raise ArgumentError, 'Duplicate Key, key for unknown value already provided.' if input.unknowns[unknown_key]
75
+ input.unknowns[unknown_key] = value
76
+ end
77
+ end
78
+ input
79
+ end
80
+
81
+ def to_payload
82
+ payload = ''
83
+ payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:non_witness_utxo], value: non_witness_utxo.to_payload) if non_witness_utxo
84
+ payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:witness_utxo], value: witness_utxo.to_payload) if witness_utxo
85
+ if final_script_sig.nil? && final_script_witness.nil?
86
+ payload << partial_sigs.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:partial_sig], key: k.htb, value: v)}.join
87
+ payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:sighash], value: [sighash_type].pack('I')) if sighash_type
88
+ payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:redeem_script], value: redeem_script.to_payload) if redeem_script
89
+ payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:witness_script], value: witness_script.to_payload) if witness_script
90
+ payload << hd_key_paths.values.map(&:to_payload).join
91
+ end
92
+ payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:script_sig], value: final_script_sig.to_payload) if final_script_sig
93
+ payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:script_witness], value: final_script_witness.to_payload) if final_script_witness
94
+ payload << unknowns.map {|k,v|Bitcoin.pack_var_int(k.htb.bytesize) << k.htb << Bitcoin.pack_var_int(v.bytesize) << v}.join
95
+ payload << PSBT_SEPARATOR.to_even_length_hex.htb
96
+ payload
97
+ end
98
+
99
+ # Sanity check
100
+ def sane?
101
+ return false if non_witness_utxo && witness_utxo
102
+ return false if witness_script && witness_utxo.nil?
103
+ return false if final_script_witness && witness_utxo.nil?
104
+ true
105
+ end
106
+
107
+ # add signature as partial sig.
108
+ # @param [String] pubkey a public key with hex format.
109
+ # @param [String] sig a signature.
110
+ def add_sig(pubkey, sig)
111
+ raise ArgumentError, 'The sighash in signature is invalid.' unless sig.unpack('C*')[-1] == sighash_type
112
+ raise ArgumentError, 'Duplicate Key, input partial signature for pubkey already provided.' if partial_sigs[pubkey]
113
+ partial_sigs[pubkey] = sig
114
+ end
115
+
116
+ # merge two PSBT inputs to create one PSBT.
117
+ # @param [Bitcoin::PSBT::Input] psbi PSBT input to be combined which must have same property in PSBT Input.
118
+ # @return [Bitcoin::PSBT::Input] combined object.
119
+ def merge(psbi)
120
+ raise ArgumentError, 'The argument psbt must be an instance of Bitcoin::PSBT::Input.' unless psbi.is_a?(Bitcoin::PSBT::Input)
121
+ raise ArgumentError, 'The Partially Signed Input\'s non_witness_utxo are different.' unless non_witness_utxo == psbi.non_witness_utxo
122
+ raise ArgumentError, 'The Partially Signed Input\'s witness_utxo are different.' unless witness_utxo == psbi.witness_utxo
123
+ raise ArgumentError, 'The Partially Signed Input\'s sighash_type are different.' unless sighash_type == psbi.sighash_type
124
+ raise ArgumentError, 'The Partially Signed Input\'s redeem_script are different.' unless redeem_script == psbi.redeem_script
125
+ raise ArgumentError, 'The Partially Signed Input\'s witness_script are different.' unless witness_script == psbi.witness_script
126
+ combined = Bitcoin::PSBT::Input.new(non_witness_utxo: non_witness_utxo, witness_utxo: witness_utxo)
127
+ combined.unknowns = unknowns.merge(psbi.unknowns)
128
+ combined.redeem_script = redeem_script
129
+ combined.witness_script = witness_script
130
+ combined.sighash_type = sighash_type
131
+ sigs = Hash[partial_sigs.merge(psbi.partial_sigs)]
132
+ redeem_script.get_multisig_pubkeys.each{|pubkey|combined.partial_sigs[pubkey.bth] = sigs[pubkey.bth]} if redeem_script && redeem_script.multisig?
133
+ witness_script.get_multisig_pubkeys.each{|pubkey|combined.partial_sigs[pubkey.bth] = sigs[pubkey.bth]} if witness_script && witness_script.multisig?
134
+ combined.hd_key_paths = hd_key_paths.merge(psbi.hd_key_paths)
135
+ combined
136
+ end
137
+
138
+ # finalize input.
139
+ # TODO This feature is experimental and support only multisig.
140
+ # @return [Bitcoin::PSBT::Input] finalized input.
141
+ def finalize!
142
+ if non_witness_utxo
143
+ self.final_script_sig = Bitcoin::Script.new << Bitcoin::Opcodes::OP_0 if redeem_script.multisig?
144
+ partial_sigs.values.each {|sig|final_script_sig << sig.bth}
145
+ final_script_sig << redeem_script.to_payload.bth
146
+ self.partial_sigs = {}
147
+ self.hd_key_paths = {}
148
+ self.redeem_script = nil
149
+ self.sighash_type = nil
150
+ else
151
+ if redeem_script
152
+ self.final_script_sig = Bitcoin::Script.parse_from_payload(Bitcoin::Script.pack_pushdata(redeem_script.to_payload))
153
+ self.redeem_script = nil
154
+ end
155
+ if witness_script
156
+ self.final_script_witness = Bitcoin::ScriptWitness.new
157
+ final_script_witness.stack << '' if witness_script.multisig?
158
+ partial_sigs.values.each {|sig| final_script_witness.stack << sig}
159
+ final_script_witness.stack << witness_script.to_payload
160
+ self.witness_script = nil
161
+ end
162
+ self.sighash_type = nil
163
+ self.partial_sigs = {}
164
+ self.hd_key_paths = {}
165
+ end
166
+ self
167
+ end
168
+
169
+ end
170
+
171
+ end
172
+ end
@@ -0,0 +1,75 @@
1
+ module Bitcoin
2
+ module PSBT
3
+
4
+ # Class for PSBTs which contains per output information
5
+ class Output
6
+
7
+ attr_accessor :redeem_script
8
+ attr_accessor :witness_script
9
+ attr_accessor :hd_key_paths
10
+ attr_accessor :unknowns
11
+
12
+ def initialize
13
+ @hd_key_paths = {}
14
+ @unknowns = {}
15
+ end
16
+
17
+ # parse PSBT output data form buffer.
18
+ # @param [StringIO] buf psbt buffer.
19
+ # @return [Bitcoin::PSBTOutput] psbt output.
20
+ def self.parse_from_buf(buf)
21
+ output = self.new
22
+ until buf.eof?
23
+ key_len = Bitcoin.unpack_var_int_from_io(buf)
24
+ break if key_len == 0
25
+ key_type = buf.read(1).unpack('C').first
26
+ key = buf.read(key_len - 1)
27
+ value = buf.read(Bitcoin.unpack_var_int_from_io(buf))
28
+ case key_type
29
+ when PSBT_OUT_TYPES[:redeem_script]
30
+ raise ArgumentError, 'Duplicate Key, output redeemScript already provided' if output.redeem_script
31
+ output.redeem_script = value
32
+ when PSBT_OUT_TYPES[:witness_script]
33
+ raise ArgumentError, 'Duplicate Key, output witnessScript already provided' if output.witness_script
34
+ output.witness_script = value
35
+ when PSBT_OUT_TYPES[:bip32_derivation]
36
+ raise ArgumentError, 'Duplicate Key, pubkey derivation path already provided' if output.hd_key_paths[key.bth]
37
+ output.hd_key_paths[key.bth] = Bitcoin::PSBT::HDKeyPath.parse_from_payload(key, value)
38
+ else
39
+ unknown_key = ([key_type].pack('C') + key).bth
40
+ raise ArgumentError, 'Duplicate Key, key for unknown value already provided' if output.unknowns[unknown_key]
41
+ output.unknowns[unknown_key] = value
42
+ end
43
+ end
44
+ output
45
+ end
46
+
47
+ def to_payload
48
+ payload = ''
49
+ payload << PSBT.serialize_to_vector(PSBT_OUT_TYPES[:redeem_script], value: redeem_script) if redeem_script
50
+ payload << PSBT.serialize_to_vector(PSBT_OUT_TYPES[:witness_script], value: witness_script) if witness_script
51
+ payload << hd_key_paths.values.map{|v|v.to_payload(PSBT_OUT_TYPES[:bip32_derivation])}.join
52
+ payload << unknowns.map {|k,v|Bitcoin.pack_var_int(k.htb.bytesize) << k.htb << Bitcoin.pack_var_int(v.bytesize) << v}.join
53
+ payload << PSBT_SEPARATOR.to_even_length_hex.htb
54
+ payload
55
+ end
56
+
57
+ # merge two PSBT outputs to create one PSBT.
58
+ # @param [Bitcoin::PSBT::Output] psbo PSBT output to be combined which must have same property in PSBT Output.
59
+ # @return [Bitcoin::PSBT::Output] combined object.
60
+ def merge(psbo)
61
+ raise ArgumentError, 'The argument psbt must be an instance of Bitcoin::PSBT::Output.' unless psbo.is_a?(Bitcoin::PSBT::Output)
62
+ raise ArgumentError, 'The Partially Signed Output\'s redeem_script are different.' unless redeem_script == psbo.redeem_script
63
+ raise ArgumentError, 'The Partially Signed Output\'s witness_script are different.' unless witness_script == psbo.witness_script
64
+ combined = Bitcoin::PSBT::Output.new
65
+ combined.redeem_script = redeem_script
66
+ combined.witness_script = witness_script
67
+ combined.unknowns = unknowns.merge(psbo.unknowns)
68
+ combined.hd_key_paths = hd_key_paths.merge(psbo.hd_key_paths)
69
+ combined
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,197 @@
1
+ module Bitcoin
2
+ module PSBT
3
+
4
+ class Tx
5
+ attr_accessor :tx
6
+ attr_reader :inputs
7
+ attr_reader :outputs
8
+ attr_accessor :unknowns
9
+
10
+ def initialize(tx = nil)
11
+ @tx = tx
12
+ @inputs = tx ? tx.in.map{Input.new}: []
13
+ @outputs = tx ? tx.out.map{Output.new}: []
14
+ @unknowns = {}
15
+ end
16
+
17
+ # parse Partially Signed Bitcoin Transaction data with Base64 format.
18
+ # @param [String] base64 a Partially Signed Bitcoin Transaction data with Base64 format.
19
+ # @return [Bitcoin::PartiallySignedTx]
20
+ def self.parse_from_base64(base64)
21
+ self.parse_from_payload(Base64.decode64(base64))
22
+ end
23
+
24
+ # parse Partially Signed Bitcoin Transaction data.
25
+ # @param [String] payload a Partially Signed Bitcoin Transaction data with binary format.
26
+ # @return [Bitcoin::PartiallySignedTx]
27
+ def self.parse_from_payload(payload)
28
+ buf = StringIO.new(payload)
29
+ raise ArgumentError, 'Invalid PSBT magic bytes.' unless buf.read(4).unpack('N').first == PSBT_MAGIC_BYTES
30
+ raise ArgumentError, 'Invalid PSBT separator.' unless buf.read(1).bth.to_i(16) == 0xff
31
+ partial_tx = self.new
32
+
33
+ # read global data.
34
+ until buf.eof?
35
+ key_len = Bitcoin.unpack_var_int_from_io(buf)
36
+ break if key_len == 0
37
+ key_type = buf.read(1).unpack('C').first
38
+ key = buf.read(key_len - 1)
39
+ value = buf.read(Bitcoin.unpack_var_int_from_io(buf))
40
+
41
+ case key_type
42
+ when PSBT_GLOBAL_TYPES[:unsigned_tx]
43
+ raise ArgumentError, 'Duplicate Key, unsigned tx already provided' if partial_tx.tx
44
+ partial_tx.tx = Bitcoin::Tx.parse_from_payload(value)
45
+ partial_tx.tx.in.each do |tx_in|
46
+ raise ArgumentError, 'Unsigned tx does not have empty scriptSigs and scriptWitnesses.' if !tx_in.script_sig.empty? || !tx_in.script_witness.empty?
47
+ end
48
+ else
49
+ raise ArgumentError, 'Duplicate Key, key for unknown value already provided' if partial_tx.unknowns[key]
50
+ partial_tx.unknowns[([key_type].pack('C') + key).bth] = value
51
+ end
52
+ end
53
+
54
+ raise ArgumentError, 'No unsigned transcation was provided.' unless partial_tx.tx
55
+
56
+ # read input data.
57
+ partial_tx.tx.in.each do |tx_in|
58
+ break if buf.eof?
59
+ input = Input.parse_from_buf(buf)
60
+ partial_tx.inputs << input
61
+ if input.non_witness_utxo && input.non_witness_utxo.hash != tx_in.prev_hash
62
+ raise ArgumentError, 'Non-witness UTXO does not match outpoint hash'
63
+ end
64
+ end
65
+
66
+ raise ArgumentError, 'Inputs provided does not match the number of inputs in transaction.' unless partial_tx.inputs.size == partial_tx.tx.in.size
67
+
68
+ # read output data.
69
+ partial_tx.tx.outputs.each do
70
+ break if buf.eof?
71
+ output = Output.parse_from_buf(buf)
72
+ break unless output
73
+ partial_tx.outputs << output
74
+ end
75
+
76
+ raise ArgumentError, 'Outputs provided does not match the number of outputs in transaction.' unless partial_tx.outputs.size == partial_tx.tx.out.size
77
+
78
+ partial_tx.inputs.each do |input|
79
+ raise ArgumentError, 'PSBT is not sane.' unless input.sane?
80
+ end
81
+
82
+ partial_tx
83
+ end
84
+
85
+ # generate payload.
86
+ # @return [String] a payload with binary format.
87
+ def to_payload
88
+ payload = PSBT_MAGIC_BYTES.to_even_length_hex.htb << 0xff.to_even_length_hex.htb
89
+
90
+ payload << PSBT.serialize_to_vector(PSBT_GLOBAL_TYPES[:unsigned_tx], value: tx.to_payload)
91
+
92
+ payload << unknowns.map {|k,v|Bitcoin.pack_var_int(k.htb.bytesize) << k.htb << Bitcoin.pack_var_int(v.bytesize) << v}.join
93
+
94
+ payload << PSBT_SEPARATOR.to_even_length_hex.htb
95
+
96
+ payload << inputs.map(&:to_payload).join
97
+ payload << outputs.map(&:to_payload).join
98
+ payload
99
+ end
100
+
101
+ # generate payload with Base64 format.
102
+ # @return [String] a payload with Base64 format.
103
+ def to_base64
104
+ Base64.strict_encode64(to_payload)
105
+ end
106
+
107
+ # update input key-value maps.
108
+ # @param [Bitcoin::Tx] prev_tx previous tx reference by input.
109
+ # @param [Bitcoin::Script] redeem_script redeem script to set input.
110
+ # @param [Bitcoin::Script] witness_script witness script to set input.
111
+ # @param [Hash] hd_key_paths bip 32 hd key paths to set input.
112
+ def update!(prev_tx, redeem_script: nil, witness_script: nil, hd_key_paths: [])
113
+ prev_hash = prev_tx.hash
114
+ tx.in.each_with_index do|tx_in, i|
115
+ if tx_in.prev_hash == prev_hash
116
+ utxo = prev_tx.out[tx_in.out_point.index]
117
+ raise ArgumentError, 'redeem script does not match utxo.' if redeem_script && !utxo.script_pubkey.include?(redeem_script.to_hash160)
118
+ raise ArgumentError, 'witness script does not match redeem script.' if redeem_script && witness_script && !redeem_script.include?(witness_script.to_sha256)
119
+ if utxo.script_pubkey.witness_program? || (redeem_script && redeem_script.witness_program?)
120
+ inputs[i].witness_utxo = utxo
121
+ else
122
+ inputs[i].non_witness_utxo = prev_tx
123
+ end
124
+ inputs[i].redeem_script = redeem_script if redeem_script
125
+ inputs[i].witness_script = witness_script if witness_script
126
+ inputs[i].hd_key_paths = hd_key_paths.map(&:pubkey).zip(hd_key_paths).to_h
127
+ end
128
+ end
129
+ end
130
+
131
+ # get signature script of input specified by +index+
132
+ # @param [Integer] index input index.
133
+ # @return [Bitcoin::Script]
134
+ def signature_script(index)
135
+ i = inputs[index]
136
+ if i.non_witness_utxo
137
+ i.redeem_script ? i.redeem_script : i.non_witness_utxo.out[tx.in[index].out_point.index].script_pubkey
138
+ else
139
+ i.witness_script ? i.witness_script : i.witness_utxo
140
+ end
141
+ end
142
+
143
+ # merge two PSBTs to create one PSBT.
144
+ # TODO This feature is experimental.
145
+ # @param [Bitcoin::PartiallySignedTx] psbt PSBT to be combined which must have same property in PartiallySignedTx.
146
+ # @return [Bitcoin::PartiallySignedTx] combined object.
147
+ def merge(psbt)
148
+ raise ArgumentError, 'The argument psbt must be an instance of Bitcoin::PSBT::Tx.' unless psbt.is_a?(Bitcoin::PSBT::Tx)
149
+ raise ArgumentError, 'The combined transactions are different.' unless tx == psbt.tx
150
+ raise ArgumentError, 'The Partially Signed Input\'s count are different.' unless inputs.size == psbt.inputs.size
151
+ raise ArgumentError, 'The Partially Signed Output\'s count are different.' unless outputs.size == psbt.outputs.size
152
+
153
+ combined = Bitcoin::PSBT::Tx.new(tx)
154
+ inputs.each_with_index do |i, index|
155
+ combined.inputs[index] = i.merge(psbt.inputs[index])
156
+ end
157
+ outputs.each_with_index do |o, index|
158
+ combined.outputs[index] = o.merge(psbt.outputs[index])
159
+ end
160
+ combined.unknowns = unknowns.merge(psbt.unknowns)
161
+ combined
162
+ end
163
+
164
+ # finalize tx.
165
+ # TODO This feature is experimental and support only multisig.
166
+ # @return [Bitcoin::PSBT::Tx] finalized PSBT.
167
+ def finalize!
168
+ inputs.each {|input|input.finalize!}
169
+ self
170
+ end
171
+
172
+ # extract final tx.
173
+ # @return [Bitcoin::Tx] final tx.
174
+ def extract_tx
175
+ extract_tx = tx.dup
176
+ inputs.each_with_index do |input, index|
177
+ extract_tx.in[index].script_sig = input.final_script_sig if input.final_script_sig
178
+ extract_tx.in[index].script_witness = input.final_script_witness if input.final_script_witness
179
+ end
180
+ # validate signature
181
+ tx.in.each_with_index do |tx_in, index|
182
+ input = inputs[index]
183
+ if input.non_witness_utxo
184
+ utxo = input.non_witness_utxo.out[tx_in.out_point.index]
185
+ raise "input[#{index}]'s signature is invalid.'" unless tx.verify_input_sig(index, utxo.script_pubkey)
186
+ else
187
+ utxo = input.witness_utxo
188
+ raise "input[#{index}]'s signature is invalid.'" unless tx.verify_input_sig(index, utxo.script_pubkey, amount: input.witness_utxo.value)
189
+ end
190
+ end
191
+ extract_tx
192
+ end
193
+
194
+ end
195
+
196
+ end
197
+ end
data/lib/bitcoin/psbt.rb CHANGED
@@ -1,248 +1,26 @@
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
1
+ require 'base64'
149
2
 
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
3
+ module Bitcoin
177
4
 
178
- payload
179
- end
5
+ module PSBT
180
6
 
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
7
+ autoload :Tx, 'bitcoin/psbt/tx'
8
+ autoload :Input, 'bitcoin/psbt/input'
9
+ autoload :Output, 'bitcoin/psbt/output'
10
+ autoload :HDKeyPath, 'bitcoin/psbt/hd_key_path'
199
11
 
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
12
+ # constants for PSBT
13
+ PSBT_MAGIC_BYTES = 0x70736274
14
+ PSBT_GLOBAL_TYPES = {unsigned_tx: 0x00}
15
+ PSBT_IN_TYPES = {non_witness_utxo: 0x00, witness_utxo: 0x01, partial_sig: 0x02,
16
+ sighash: 0x03, redeem_script: 0x04, witness_script: 0x05,
17
+ bip32_derivation: 0x06, script_sig: 0x07, script_witness: 0x08}
18
+ PSBT_OUT_TYPES = {redeem_script: 0x00, witness_script: 0x01, bip32_derivation: 0x02}
19
+ PSBT_SEPARATOR = 0x00
242
20
 
243
- private
21
+ module_function
244
22
 
245
- def serialize_to_vector(key_type, key: nil, value: nil)
23
+ def self.serialize_to_vector(key_type, key: nil, value: nil)
246
24
  key_len = key_type.to_even_length_hex.htb.bytesize
247
25
  key_len += key.bytesize if key
248
26
  s = Bitcoin.pack_var_int(key_len) << Bitcoin.pack_var_int(key_type)
@@ -250,44 +28,6 @@ module Bitcoin
250
28
  s << Bitcoin.pack_var_int(value.bytesize) << value
251
29
  s
252
30
  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
31
  end
292
32
 
293
33
  end
@@ -32,10 +32,17 @@ module Bitcoin
32
32
  [redeem_script.to_p2sh, redeem_script]
33
33
  end
34
34
 
35
+ # generate p2sh script.
36
+ # @param [String] script_hash script hash for P2SH
37
+ # @return [Script] P2SH script
38
+ def self.to_p2sh(script_hash)
39
+ Script.new << OP_HASH160 << script_hash << OP_EQUAL
40
+ end
41
+
35
42
  # generate p2sh script with this as a redeem script
36
43
  # @return [Script] P2SH script
37
44
  def to_p2sh
38
- Script.new << OP_HASH160 << to_hash160 << OP_EQUAL
45
+ Script.to_p2sh(to_hash160)
39
46
  end
40
47
 
41
48
  def get_multisig_pubkeys
@@ -73,6 +80,27 @@ module Bitcoin
73
80
  script
74
81
  end
75
82
 
83
+ # generate script from addr.
84
+ # @param [String] addr address.
85
+ # @return [Bitcoin::Script] parsed script.
86
+ def self.parse_from_addr(addr)
87
+ begin
88
+ segwit_addr = Bech32::SegwitAddr.new(addr)
89
+ raise 'Invalid hrp.' unless Bitcoin.chain_params.bech32_hrp == segwit_addr.hrp
90
+ Bitcoin::Script.parse_from_payload(segwit_addr.to_script_pubkey.htb)
91
+ rescue Exception => e
92
+ hex, addr_version = Bitcoin.decode_base58_address(addr)
93
+ case addr_version
94
+ when Bitcoin.chain_params.address_version
95
+ Bitcoin::Script.to_p2pkh(hex)
96
+ when Bitcoin.chain_params.p2sh_version
97
+ Bitcoin::Script.to_p2sh(hex)
98
+ else
99
+ throw e
100
+ end
101
+ end
102
+ end
103
+
76
104
  def self.parse_from_payload(payload)
77
105
  s = new
78
106
  buf = StringIO.new(payload)
@@ -271,6 +299,18 @@ module Bitcoin
271
299
  self
272
300
  end
273
301
 
302
+ # Check the item is in the chunk of the script.
303
+ def include?(item)
304
+ chunk_item = if item.is_a?(Integer)
305
+ item.chr
306
+ elsif item.is_a?(String)
307
+ data = Encoding::ASCII_8BIT == item.encoding ? item : item.htb
308
+ Bitcoin::Script.pack_pushdata(data)
309
+ end
310
+ return false unless chunk_item
311
+ chunks.include?(chunk_item)
312
+ end
313
+
274
314
  def to_s
275
315
  chunks.map { |c|
276
316
  case c
@@ -9,6 +9,15 @@ module Bitcoin
9
9
  @stack = stack
10
10
  end
11
11
 
12
+ def self.parse_from_payload(payload)
13
+ buf = payload.is_a?(StringIO) ? payload : StringIO.new(payload)
14
+ size = Bitcoin.unpack_var_int_from_io(buf)
15
+ stack = size.times.map do
16
+ buf.read(Bitcoin.unpack_var_int_from_io(buf))
17
+ end
18
+ self.new(stack)
19
+ end
20
+
12
21
  def empty?
13
22
  stack.empty?
14
23
  end
data/lib/bitcoin/tx.rb CHANGED
@@ -60,11 +60,7 @@ module Bitcoin
60
60
 
61
61
  if witness
62
62
  in_count.times do |i|
63
- witness_count = Bitcoin.unpack_var_int_from_io(buf)
64
- witness_count.times do
65
- size = Bitcoin.unpack_var_int_from_io(buf)
66
- tx.inputs[i].script_witness.stack << buf.read(size)
67
- end
63
+ tx.inputs[i].script_witness = Bitcoin::ScriptWitness.parse_from_payload(buf)
68
64
  end
69
65
  end
70
66
 
data/lib/bitcoin/tx_in.rb CHANGED
@@ -75,6 +75,12 @@ module Bitcoin
75
75
  to_payload == other.to_payload
76
76
  end
77
77
 
78
+ # return previous output hash (not txid)
79
+ def prev_hash
80
+ return nil unless out_point
81
+ out_point.hash
82
+ end
83
+
78
84
  end
79
85
 
80
86
  end
data/lib/bitcoin/util.rb CHANGED
@@ -85,11 +85,28 @@ module Bitcoin
85
85
  Digest::RMD160.hexdigest(Digest::SHA256.digest(hex.htb))
86
86
  end
87
87
 
88
+ # encode Base58 check address.
89
+ # @param [String] hex the address payload.
90
+ # @param [String] addr_version the address version for P2PKH and P2SH.
91
+ # @return [String] Base58 check encoding address.
88
92
  def encode_base58_address(hex, addr_version)
89
93
  base = addr_version + hex
90
94
  Base58.encode(base + calc_checksum(base))
91
95
  end
92
96
 
97
+ # decode Base58 check encoding address.
98
+ # @param [String] addr address.
99
+ # @return [Array] hex and address version
100
+ def decode_base58_address(addr)
101
+ hex = Base58.decode(addr)
102
+ if hex.size == 50 && calc_checksum(hex[0...-8]) == hex[-8..-1]
103
+ raise 'Invalid version bytes.' unless [Bitcoin.chain_params.address_version, Bitcoin.chain_params.p2sh_version].include?(hex[0..1])
104
+ [hex[2...-8], hex[0..1]]
105
+ else
106
+ raise 'Invalid address.'
107
+ end
108
+ end
109
+
93
110
  def calc_checksum(hex)
94
111
  double_sha256(hex.htb).bth[0..7]
95
112
  end
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "0.1.9"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/bitcoin.rb CHANGED
@@ -46,8 +46,7 @@ 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
+ autoload :PSBT, 'bitcoin/psbt'
51
50
 
52
51
  require_relative 'bitcoin/constants'
53
52
 
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenAssets
4
+ module Util
5
+ class << self
6
+ OA_VERSION_BYTE = '17' # 0x23
7
+ OA_VERSION_BYTE_TESTNET = '73' # 0x115
8
+
9
+ def script_to_asset_id(script)
10
+ hash_to_asset_id(Bitcoin.hash160(script))
11
+ end
12
+
13
+ private
14
+
15
+ def hash_to_asset_id(hash)
16
+ hash = oa_version_byte + hash
17
+ Bitcoin::Base58.encode(hash + Bitcoin.calc_checksum(hash))
18
+ end
19
+
20
+ def oa_version_byte
21
+ case Bitcoin.chain_params.network
22
+ when 'mainnet' then OA_VERSION_BYTE
23
+ when 'testnet', 'regtest' then OA_VERSION_BYTE_TESTNET
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
data/lib/openassets.rb CHANGED
@@ -4,5 +4,6 @@ module OpenAssets
4
4
 
5
5
  autoload :MarkerOutput, 'openassets/marker_output'
6
6
  autoload :Payload, 'openassets/payload'
7
+ autoload :Util, 'openassets/util'
7
8
 
8
9
  end
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.9
4
+ version: 0.2.0
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-22 00:00:00.000000000 Z
11
+ date: 2018-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ecdsa
@@ -366,6 +366,10 @@ files:
366
366
  - lib/bitcoin/payments/payment_request.pb.rb
367
367
  - lib/bitcoin/payments/x509_certificates.pb.rb
368
368
  - lib/bitcoin/psbt.rb
369
+ - lib/bitcoin/psbt/hd_key_path.rb
370
+ - lib/bitcoin/psbt/input.rb
371
+ - lib/bitcoin/psbt/output.rb
372
+ - lib/bitcoin/psbt/tx.rb
369
373
  - lib/bitcoin/rpc.rb
370
374
  - lib/bitcoin/rpc/http_server.rb
371
375
  - lib/bitcoin/rpc/request_handler.rb
@@ -397,6 +401,7 @@ files:
397
401
  - lib/openassets.rb
398
402
  - lib/openassets/marker_output.rb
399
403
  - lib/openassets/payload.rb
404
+ - lib/openassets/util.rb
400
405
  homepage: https://github.com/haw-itn/bitcoinrb
401
406
  licenses:
402
407
  - MIT