bitcoinrb 0.1.9 → 0.2.0

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