Class: Bitcoin::PSBT::Tx
Instance Attribute Summary collapse
-
#inputs ⇒ Object
readonly
Returns the value of attribute inputs.
-
#outputs ⇒ Object
readonly
Returns the value of attribute outputs.
-
#tx ⇒ Object
Returns the value of attribute tx.
-
#unknowns ⇒ Object
Returns the value of attribute unknowns.
Class Method Summary collapse
-
.parse_from_base64(base64) ⇒ Bitcoin::PartiallySignedTx
parse Partially Signed Bitcoin Transaction data with Base64 format.
-
.parse_from_payload(payload) ⇒ Bitcoin::PartiallySignedTx
parse Partially Signed Bitcoin Transaction data.
Instance Method Summary collapse
-
#extract_tx ⇒ Bitcoin::Tx
extract final tx.
-
#finalize! ⇒ Bitcoin::PSBT::Tx
finalize tx.
-
#initialize(tx = nil) ⇒ Tx
constructor
A new instance of Tx.
-
#merge(psbt) ⇒ Bitcoin::PartiallySignedTx
merge two PSBTs to create one PSBT.
-
#signature_script(index) ⇒ Bitcoin::Script
get signature script of input specified by
index
. -
#to_base64 ⇒ String
generate payload with Base64 format.
-
#to_payload ⇒ String
generate payload.
-
#update!(prev_tx, redeem_script: nil, witness_script: nil, hd_key_paths: []) ⇒ Object
update input key-value maps.
Constructor Details
#initialize(tx = nil) ⇒ Tx
Returns a new instance of Tx.
10 11 12 13 14 15 |
# File 'lib/bitcoin/psbt/tx.rb', line 10 def initialize(tx = nil) @tx = tx @inputs = tx ? tx.in.map{Input.new}: [] @outputs = tx ? tx.out.map{Output.new}: [] @unknowns = {} end |
Instance Attribute Details
#inputs ⇒ Object (readonly)
Returns the value of attribute inputs.
6 7 8 |
# File 'lib/bitcoin/psbt/tx.rb', line 6 def inputs @inputs end |
#outputs ⇒ Object (readonly)
Returns the value of attribute outputs.
7 8 9 |
# File 'lib/bitcoin/psbt/tx.rb', line 7 def outputs @outputs end |
#tx ⇒ Object
Returns the value of attribute tx.
5 6 7 |
# File 'lib/bitcoin/psbt/tx.rb', line 5 def tx @tx end |
#unknowns ⇒ Object
Returns the value of attribute unknowns.
8 9 10 |
# File 'lib/bitcoin/psbt/tx.rb', line 8 def unknowns @unknowns end |
Class Method Details
.parse_from_base64(base64) ⇒ Bitcoin::PartiallySignedTx
parse Partially Signed Bitcoin Transaction data with Base64 format.
20 21 22 |
# File 'lib/bitcoin/psbt/tx.rb', line 20 def self.parse_from_base64(base64) self.parse_from_payload(Base64.decode64(base64)) end |
.parse_from_payload(payload) ⇒ Bitcoin::PartiallySignedTx
parse Partially Signed Bitcoin Transaction data.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/bitcoin/psbt/tx.rb', line 27 def self.parse_from_payload(payload) buf = StringIO.new(payload) raise ArgumentError, 'Invalid PSBT magic bytes.' unless buf.read(4).unpack('N').first == PSBT_MAGIC_BYTES raise ArgumentError, 'Invalid PSBT separator.' unless buf.read(1).bth.to_i(16) == 0xff partial_tx = self.new # read global data. until buf.eof? key_len = Bitcoin.unpack_var_int_from_io(buf) break if key_len == 0 key_type = buf.read(1).unpack('C').first key = buf.read(key_len - 1) value = buf.read(Bitcoin.unpack_var_int_from_io(buf)) case key_type when PSBT_GLOBAL_TYPES[:unsigned_tx] raise ArgumentError, 'Duplicate Key, unsigned tx already provided' if partial_tx.tx partial_tx.tx = Bitcoin::Tx.parse_from_payload(value) partial_tx.tx.in.each do |tx_in| raise ArgumentError, 'Unsigned tx does not have empty scriptSigs and scriptWitnesses.' if !tx_in.script_sig.empty? || !tx_in.script_witness.empty? end else raise ArgumentError, 'Duplicate Key, key for unknown value already provided' if partial_tx.unknowns[key] partial_tx.unknowns[([key_type].pack('C') + key).bth] = value end end raise ArgumentError, 'No unsigned transcation was provided.' unless partial_tx.tx # read input data. partial_tx.tx.in.each do |tx_in| break if buf.eof? input = Input.parse_from_buf(buf) partial_tx.inputs << input if input.non_witness_utxo && input.non_witness_utxo.hash != tx_in.prev_hash raise ArgumentError, 'Non-witness UTXO does not match outpoint hash' end end raise ArgumentError, 'Inputs provided does not match the number of inputs in transaction.' unless partial_tx.inputs.size == partial_tx.tx.in.size # read output data. partial_tx.tx.outputs.each do break if buf.eof? output = Output.parse_from_buf(buf) break unless output partial_tx.outputs << output end raise ArgumentError, 'Outputs provided does not match the number of outputs in transaction.' unless partial_tx.outputs.size == partial_tx.tx.out.size partial_tx.inputs.each do |input| raise ArgumentError, 'PSBT is not sane.' unless input.sane? end partial_tx end |
Instance Method Details
#extract_tx ⇒ Bitcoin::Tx
extract final tx.
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/bitcoin/psbt/tx.rb', line 174 def extract_tx extract_tx = tx.dup inputs.each_with_index do |input, index| extract_tx.in[index].script_sig = input.final_script_sig if input.final_script_sig extract_tx.in[index].script_witness = input.final_script_witness if input.final_script_witness end # validate signature tx.in.each_with_index do |tx_in, index| input = inputs[index] if input.non_witness_utxo utxo = input.non_witness_utxo.out[tx_in.out_point.index] raise "input[#{index}]'s signature is invalid.'" unless tx.verify_input_sig(index, utxo.script_pubkey) else utxo = input.witness_utxo raise "input[#{index}]'s signature is invalid.'" unless tx.verify_input_sig(index, utxo.script_pubkey, amount: input.witness_utxo.value) end end extract_tx end |
#finalize! ⇒ Bitcoin::PSBT::Tx
finalize tx. TODO This feature is experimental and support only multisig.
167 168 169 170 |
# File 'lib/bitcoin/psbt/tx.rb', line 167 def finalize! inputs.each {|input|input.finalize!} self end |
#merge(psbt) ⇒ Bitcoin::PartiallySignedTx
merge two PSBTs to create one PSBT. TODO This feature is experimental.
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/bitcoin/psbt/tx.rb', line 147 def merge(psbt) raise ArgumentError, 'The argument psbt must be an instance of Bitcoin::PSBT::Tx.' unless psbt.is_a?(Bitcoin::PSBT::Tx) raise ArgumentError, 'The combined transactions are different.' unless tx == psbt.tx raise ArgumentError, 'The Partially Signed Input\'s count are different.' unless inputs.size == psbt.inputs.size raise ArgumentError, 'The Partially Signed Output\'s count are different.' unless outputs.size == psbt.outputs.size combined = Bitcoin::PSBT::Tx.new(tx) inputs.each_with_index do |i, index| combined.inputs[index] = i.merge(psbt.inputs[index]) end outputs.each_with_index do |o, index| combined.outputs[index] = o.merge(psbt.outputs[index]) end combined.unknowns = unknowns.merge(psbt.unknowns) combined end |
#signature_script(index) ⇒ Bitcoin::Script
get signature script of input specified by index
134 135 136 137 138 139 140 141 |
# File 'lib/bitcoin/psbt/tx.rb', line 134 def signature_script(index) i = inputs[index] if i.non_witness_utxo i.redeem_script ? i.redeem_script : i.non_witness_utxo.out[tx.in[index].out_point.index].script_pubkey else i.witness_script ? i.witness_script : i.witness_utxo end end |
#to_base64 ⇒ String
generate payload with Base64 format.
103 104 105 |
# File 'lib/bitcoin/psbt/tx.rb', line 103 def to_base64 Base64.strict_encode64(to_payload) end |
#to_payload ⇒ String
generate payload.
87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/bitcoin/psbt/tx.rb', line 87 def to_payload payload = PSBT_MAGIC_BYTES.to_even_length_hex.htb << 0xff.to_even_length_hex.htb payload << PSBT.serialize_to_vector(PSBT_GLOBAL_TYPES[:unsigned_tx], value: tx.to_payload) payload << unknowns.map {|k,v|Bitcoin.pack_var_int(k.htb.bytesize) << k.htb << Bitcoin.pack_var_int(v.bytesize) << v}.join payload << PSBT_SEPARATOR.to_even_length_hex.htb payload << inputs.map(&:to_payload).join payload << outputs.map(&:to_payload).join payload end |
#update!(prev_tx, redeem_script: nil, witness_script: nil, hd_key_paths: []) ⇒ Object
update input key-value maps.
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/bitcoin/psbt/tx.rb', line 112 def update!(prev_tx, redeem_script: nil, witness_script: nil, hd_key_paths: []) prev_hash = prev_tx.hash tx.in.each_with_index do|tx_in, i| if tx_in.prev_hash == prev_hash utxo = prev_tx.out[tx_in.out_point.index] raise ArgumentError, 'redeem script does not match utxo.' if redeem_script && !utxo.script_pubkey.include?(redeem_script.to_hash160) raise ArgumentError, 'witness script does not match redeem script.' if redeem_script && witness_script && !redeem_script.include?(witness_script.to_sha256) if utxo.script_pubkey.witness_program? || (redeem_script && redeem_script.witness_program?) inputs[i].witness_utxo = utxo else inputs[i].non_witness_utxo = prev_tx end inputs[i].redeem_script = redeem_script if redeem_script inputs[i].witness_script = witness_script if witness_script inputs[i].hd_key_paths = hd_key_paths.map(&:pubkey).zip(hd_key_paths).to_h end end end |