Class: Tapyrus::PSTT::Input

Inherits:
Object
  • Object
show all
Defined in:
lib/tapyrus/pstt/input.rb

Overview

An input map of a PSTT.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(out_point: nil, utxo: nil, sequence: nil) ⇒ Input

Returns a new instance of Input.

Parameters:



75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/tapyrus/pstt/input.rb', line 75

def initialize(out_point: nil, utxo: nil, sequence: nil)
  @out_point = out_point
  @utxo = utxo
  @sequence = sequence
  @partial_sigs = {}
  @bip32_derivations = {}
  @ripemd160_preimages = {}
  @sha256_preimages = {}
  @hash160_preimages = {}
  @hash256_preimages = {}
  @proprietaries = []
  @unknowns = {}
  @record_order = []
end

Instance Attribute Details

#bip32_derivationsObject (readonly)

Returns the value of attribute bip32_derivations.



43
44
45
# File 'lib/tapyrus/pstt/input.rb', line 43

def bip32_derivations
  @bip32_derivations
end

#final_script_sigTapyrus::Script

Returns the complete scriptSig for this input.

Returns:



23
24
25
# File 'lib/tapyrus/pstt/input.rb', line 23

def final_script_sig
  @final_script_sig
end

#hash160_preimagesObject (readonly)

Returns the value of attribute hash160_preimages.



55
56
57
# File 'lib/tapyrus/pstt/input.rb', line 55

def hash160_preimages
  @hash160_preimages
end

#hash256_preimagesObject (readonly)

Returns the value of attribute hash256_preimages.



59
60
61
# File 'lib/tapyrus/pstt/input.rb', line 59

def hash256_preimages
  @hash256_preimages
end

#out_pointTapyrus::OutPoint

Returns PSTT_IN_PREVIOUS_TXID and PSTT_IN_OUTPUT_INDEX.

Returns:



7
8
9
# File 'lib/tapyrus/pstt/input.rb', line 7

def out_point
  @out_point
end

#partial_sigsObject (readonly)

Returns the value of attribute partial_sigs.



39
40
41
# File 'lib/tapyrus/pstt/input.rb', line 39

def partial_sigs
  @partial_sigs
end

#proprietariesObject (readonly)

Returns the value of attribute proprietaries.



63
64
65
# File 'lib/tapyrus/pstt/input.rb', line 63

def proprietaries
  @proprietaries
end

#record_orderArray[String]

Returns the complete keys of the map this input was parsed from. See Tapyrus::PSTT.order_records.

Returns:

  • (Array[String])

    the complete keys of the map this input was parsed from. See Tapyrus::PSTT.order_records.



72
73
74
# File 'lib/tapyrus/pstt/input.rb', line 72

def record_order
  @record_order
end

#redeem_scriptTapyrus::Script

Returns the redeem script, if the output being spent is P2SH or CP2SH.

Returns:

  • (Tapyrus::Script)

    the redeem script, if the output being spent is P2SH or CP2SH.



19
20
21
# File 'lib/tapyrus/pstt/input.rb', line 19

def redeem_script
  @redeem_script
end

#required_height_locktimeInteger

Returns the minimum block height the transaction's locktime must be.

Returns:

  • (Integer)

    the minimum block height the transaction's locktime must be.



35
36
37
# File 'lib/tapyrus/pstt/input.rb', line 35

def required_height_locktime
  @required_height_locktime
end

#required_time_locktimeInteger

Returns the minimum Unix timestamp the transaction's locktime must be.

Returns:

  • (Integer)

    the minimum Unix timestamp the transaction's locktime must be.



31
32
33
# File 'lib/tapyrus/pstt/input.rb', line 31

def required_time_locktime
  @required_time_locktime
end

#ripemd160_preimagesObject (readonly)

Returns the value of attribute ripemd160_preimages.



47
48
49
# File 'lib/tapyrus/pstt/input.rb', line 47

def ripemd160_preimages
  @ripemd160_preimages
end

#sequenceInteger

Returns the sequence number. nil means the default 0xFFFFFFFF.

Returns:

  • (Integer)

    the sequence number. nil means the default 0xFFFFFFFF.



27
28
29
# File 'lib/tapyrus/pstt/input.rb', line 27

def sequence
  @sequence
end

#sha256_preimagesObject (readonly)

Returns the value of attribute sha256_preimages.



51
52
53
# File 'lib/tapyrus/pstt/input.rb', line 51

def sha256_preimages
  @sha256_preimages
end

#sighash_typeInteger

Returns the sighash type signers must use for this input.

Returns:

  • (Integer)

    the sighash type signers must use for this input.



15
16
17
# File 'lib/tapyrus/pstt/input.rb', line 15

def sighash_type
  @sighash_type
end

#unknownsObject (readonly)

Returns the value of attribute unknowns.



67
68
69
# File 'lib/tapyrus/pstt/input.rb', line 67

def unknowns
  @unknowns
end

#utxoTapyrus::Tx

Returns the complete previous transaction which contains the output being spent.

Returns:

  • (Tapyrus::Tx)

    the complete previous transaction which contains the output being spent.



11
12
13
# File 'lib/tapyrus/pstt/input.rb', line 11

def utxo
  @utxo
end

Class Method Details

.commits_to_all_sequences?(sig) ⇒ Boolean

Whether the given signature commits to the sequence number of every input. A SIGHASH_ALL signature without SIGHASH_ANYONECANPAY does.

Parameters:

  • sig (String)

    a signature with binary format.

Returns:

  • (Boolean)


329
330
331
332
333
# File 'lib/tapyrus/pstt/input.rb', line 329

def self.commits_to_all_sequences?(sig)
  return false if sig.nil? || sig.empty?
  hash_type = sig[-1].unpack1("C")
  (hash_type & SIGHASH_TYPE[:anyonecanpay]).zero? && (hash_type & 0x1f) == SIGHASH_TYPE[:all]
end

.parse_from_records(records) ⇒ Tapyrus::PSTT::Input

Build an input map from the records of an input map.

Parameters:

Returns:

Raises:



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/tapyrus/pstt/input.rb', line 94

def self.parse_from_records(records)
  input = new
  txid = nil
  index = nil
  records.each do |record|
    if InputTypes::RESERVED.include?(record.type)
      raise Error, format("Input key type 0x%02x is reserved.", record.type)
    end
    case record.type
    when InputTypes::UTXO
      PSTT.validate_empty_keydata!(record)
      input.utxo = parse_utxo(record.value)
    when InputTypes::PARTIAL_SIG
      PSTT.validate_pubkey_keydata!(record)
      if record.value.empty?
        raise Error, "PSTT_IN_PARTIAL_SIG must carry a signature followed by the 1-byte sighash type."
      end
      input.partial_sigs[record.keydata.bth] = record.value
    when InputTypes::SIGHASH_TYPE
      PSTT.validate_empty_keydata!(record)
      input.sighash_type = PSTT.read_u32(record)
      PSTT.validate_sighash_type!(input.sighash_type)
    when InputTypes::REDEEM_SCRIPT
      PSTT.validate_empty_keydata!(record)
      input.redeem_script =
        PSTT.parse_field("PSTT_IN_REDEEM_SCRIPT") { Tapyrus::Script.parse_from_payload(record.value) }
    when InputTypes::BIP32_DERIVATION
      PSTT.validate_pubkey_keydata!(record)
      input.bip32_derivations[record.keydata.bth] = KeyOriginInfo.parse_from_payload(record.value)
    when InputTypes::FINAL_SCRIPTSIG
      PSTT.validate_empty_keydata!(record)
      input.final_script_sig =
        PSTT.parse_field("PSTT_IN_FINAL_SCRIPTSIG") { Tapyrus::Script.parse_from_payload(record.value) }
    when InputTypes::RIPEMD160, InputTypes::HASH160
      raise Error, "The preimage hash must be 20 bytes." unless record.keydata.bytesize == 20
      input.preimages_for(record.type)[record.keydata.bth] = record.value
    when InputTypes::SHA256, InputTypes::HASH256
      raise Error, "The preimage hash must be 32 bytes." unless record.keydata.bytesize == 32
      input.preimages_for(record.type)[record.keydata.bth] = record.value
    when InputTypes::PREVIOUS_TXID
      PSTT.validate_empty_keydata!(record)
      PSTT.validate_value_size!(record, 32)
      txid = record.value.bth
    when InputTypes::OUTPUT_INDEX
      PSTT.validate_empty_keydata!(record)
      index = PSTT.read_u32(record)
    when InputTypes::SEQUENCE
      PSTT.validate_empty_keydata!(record)
      input.sequence = PSTT.read_u32(record)
    when InputTypes::REQUIRED_TIME_LOCKTIME
      PSTT.validate_empty_keydata!(record)
      input.required_time_locktime = PSTT.read_u32(record)
      PSTT.validate_time_locktime!(input.required_time_locktime)
    when InputTypes::REQUIRED_HEIGHT_LOCKTIME
      PSTT.validate_empty_keydata!(record)
      input.required_height_locktime = PSTT.read_u32(record)
      PSTT.validate_height_locktime!(input.required_height_locktime)
    when InputTypes::PROPRIETARY
      input.proprietaries << Proprietary.parse_from_record(record)
    else
      input.unknowns[record.key] = record.value
    end
  end
  raise Error, "PSTT_IN_PREVIOUS_TXID is required." unless txid
  raise Error, "PSTT_IN_OUTPUT_INDEX is required." unless index
  input.out_point = Tapyrus::OutPoint.new(txid, index)
  input.record_order = records.map(&:key)
  input.validate_sighash_type!
  input
end

Instance Method Details

#==(other) ⇒ Object



372
373
374
# File 'lib/tapyrus/pstt/input.rb', line 372

def ==(other)
  other.is_a?(Input) && to_payload == other.to_payload
end

#final_sequenceInteger

Returns the sequence number used in the transaction.

Returns:

  • (Integer)

    the sequence number used in the transaction.



218
219
220
# File 'lib/tapyrus/pstt/input.rb', line 218

def final_sequence
  sequence || Tapyrus::TxIn::SEQUENCE_FINAL
end

#finalize!(verified_sigs) ⇒ Tapyrus::PSTT::Input

Assemble the complete scriptSig from the collected records and store it in PSTT_IN_FINAL_SCRIPTSIG. The records which are no longer needed are removed.

Parameters:

  • verified_sigs (Hash)

    the signatures which verify against the transaction, keyed by public key with hex format. Deciding that needs the whole transaction, which an input map does not hold, so Tapyrus::PSTT::Tx#finalize! computes it and passes it here.

Returns:

Raises:



342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/tapyrus/pstt/input.rb', line 342

def finalize!(verified_sigs)
  return self if finalized?
  validate_for_sign!
  self.final_script_sig = build_final_script_sig(verified_sigs)
  partial_sigs.clear
  bip32_derivations.clear
  ripemd160_preimages.clear
  sha256_preimages.clear
  hash160_preimages.clear
  hash256_preimages.clear
  self.sighash_type = nil
  self.redeem_script = nil
  self
end

#finalized?Boolean

Returns whether this input has been finalized.

Returns:

  • (Boolean)

    whether this input has been finalized.



234
235
236
# File 'lib/tapyrus/pstt/input.rb', line 234

def finalized?
  !final_script_sig.nil?
end

#preimages_for(type) ⇒ Hash

Returns the preimages of the type.

Parameters:

  • type (Integer)

    a preimage key type.

Returns:

  • (Hash)

    the preimages of the type.



359
360
361
362
363
364
365
366
367
368
369
370
# File 'lib/tapyrus/pstt/input.rb', line 359

def preimages_for(type)
  case type
  when InputTypes::RIPEMD160
    ripemd160_preimages
  when InputTypes::SHA256
    sha256_preimages
  when InputTypes::HASH160
    hash160_preimages
  when InputTypes::HASH256
    hash256_preimages
  end
end

#script_codeTapyrus::Script

The scriptCode used to compute the signature hash of this input.

Returns:

Raises:



241
242
243
244
245
246
247
# File 'lib/tapyrus/pstt/input.rb', line 241

def script_code
  script = spent_script_pubkey
  return script unless script.p2sh? || script.cp2sh?
  raise Error, "PSTT_IN_REDEEM_SCRIPT is required to spend a P2SH or CP2SH output." unless redeem_script
  validate_redeem_script!
  redeem_script
end

#signed?Boolean

Returns whether this input has at least one signature.

Returns:

  • (Boolean)

    whether this input has at least one signature.



229
230
231
# File 'lib/tapyrus/pstt/input.rb', line 229

def signed?
  !partial_sigs.empty?
end

#spent_script_pubkeyTapyrus::Script

The scriptPubKey of the output being spent.

Returns:

Raises:



252
253
254
255
# File 'lib/tapyrus/pstt/input.rb', line 252

def spent_script_pubkey
  validate_utxo!
  utxo_output.script_pubkey
end

#to_payloadObject



213
214
215
# File 'lib/tapyrus/pstt/input.rb', line 213

def to_payload
  PSTT.serialize_map(to_records, record_order)
end

#to_recordsArray[Tapyrus::PSTT::KeyValue]

Returns the records of this input map.

Returns:

Raises:



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/tapyrus/pstt/input.rb', line 179

def to_records
  validate_sighash_type!
  records = []
  records << KeyValue.new(InputTypes::UTXO, "".b, utxo.to_payload) if utxo
  partial_sigs.each { |pubkey, sig| records << KeyValue.new(InputTypes::PARTIAL_SIG, pubkey.htb, sig) }
  records << KeyValue.new(InputTypes::SIGHASH_TYPE, "".b, [sighash_type].pack("V")) if sighash_type
  records << KeyValue.new(InputTypes::REDEEM_SCRIPT, "".b, redeem_script.to_payload) if redeem_script
  bip32_derivations.each do |pubkey, info|
    records << KeyValue.new(InputTypes::BIP32_DERIVATION, pubkey.htb, info.to_payload)
  end
  records << KeyValue.new(InputTypes::FINAL_SCRIPTSIG, "".b, final_script_sig.to_payload) if final_script_sig
  {
    InputTypes::RIPEMD160 => ripemd160_preimages,
    InputTypes::SHA256 => sha256_preimages,
    InputTypes::HASH160 => hash160_preimages,
    InputTypes::HASH256 => hash256_preimages
  }.each { |type, preimages| preimages.each { |hash, image| records << KeyValue.new(type, hash.htb, image) } }
  raise Error, "PSTT_IN_PREVIOUS_TXID is required." unless out_point
  records << KeyValue.new(InputTypes::PREVIOUS_TXID, "".b, out_point.tx_hash.htb)
  records << KeyValue.new(InputTypes::OUTPUT_INDEX, "".b, [out_point.index].pack("V"))
  records << KeyValue.new(InputTypes::SEQUENCE, "".b, [sequence].pack("V")) if sequence
  if required_time_locktime
    PSTT.validate_time_locktime!(required_time_locktime)
    records << KeyValue.new(InputTypes::REQUIRED_TIME_LOCKTIME, "".b, [required_time_locktime].pack("V"))
  end
  if required_height_locktime
    PSTT.validate_height_locktime!(required_height_locktime)
    records << KeyValue.new(InputTypes::REQUIRED_HEIGHT_LOCKTIME, "".b, [required_height_locktime].pack("V"))
  end
  proprietaries.each { |p| records << p.to_record(InputTypes::PROPRIETARY) }
  unknowns.each { |key, value| records << unknown_record(key, value) }
  records
end

#utxo_outputTapyrus::TxOut

Returns the output being spent, or nil if PSTT_IN_UTXO is absent.

Returns:

  • (Tapyrus::TxOut)

    the output being spent, or nil if PSTT_IN_UTXO is absent.



223
224
225
226
# File 'lib/tapyrus/pstt/input.rb', line 223

def utxo_output
  return nil unless utxo
  utxo.outputs[out_point.index]
end

#validate_for_sign!Object

Check everything a Signer must verify before signing this input.

Raises:



307
308
309
310
311
# File 'lib/tapyrus/pstt/input.rb', line 307

def validate_for_sign!
  validate_utxo!
  validate_redeem_script!
  validate_sighash_type!
end

#validate_redeem_script!Object

Check that the redeem script hashes to the value committed in the scriptPubKey being spent.

Raises:



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/tapyrus/pstt/input.rb', line 267

def validate_redeem_script!
  return unless redeem_script
  script = spent_script_pubkey
  committed =
    if script.p2sh?
      script.chunks[1].pushed_data
    elsif script.cp2sh?
      script.chunks[3].pushed_data
    else
      raise Error, "PSTT_IN_REDEEM_SCRIPT is set but the output being spent is neither P2SH nor CP2SH."
    end
  unless redeem_script.to_hash160 == committed.bth
    raise Error, "PSTT_IN_REDEEM_SCRIPT does not hash to the value committed in the scriptPubKey."
  end
end

#validate_sighash_type!Object

Check that this input requests a sighash type TIP-0174 defines, and that every signature it carries uses it. TIP-0174 obliges a Signer to use PSTT_IN_SIGHASH_TYPE, so an input which carries a signature made with another type contradicts itself: the signature covers a different transaction than the input asks for.

Raises:



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/tapyrus/pstt/input.rb', line 288

def validate_sighash_type!
  return unless sighash_type
  PSTT.validate_sighash_type!(sighash_type)
  partial_sigs.each do |pubkey, sig|
    next if sig.empty?
    hash_type = sig[-1].unpack1("C")
    next if hash_type == sighash_type
    raise Error,
          format(
            "PSTT_IN_PARTIAL_SIG for %s uses sighash type 0x%x, but PSTT_IN_SIGHASH_TYPE requires 0x%x.",
            pubkey,
            hash_type,
            sighash_type
          )
  end
end

#validate_signature_algo!(algo) ⇒ Object

Check that algo does not conflict with the scheme of the signatures already collected. Tapyrus does not allow mixing ECDSA and Schnorr signatures within a single OP_CHECKMULTISIG.

Parameters:

  • algo (Symbol)

    :ecdsa or :schnorr.

Raises:



317
318
319
320
321
322
323
# File 'lib/tapyrus/pstt/input.rb', line 317

def validate_signature_algo!(algo)
  return if partial_sigs.empty?
  current = partial_sigs.values.first.bytesize == 65 ? :schnorr : :ecdsa
  unless current == algo
    raise Error, "This input already has #{current} signatures. #{algo} signatures must not be mixed."
  end
end

#validate_utxo!Object

Check that PSTT_IN_UTXO is present and identifies the output being spent.

Raises:



259
260
261
262
263
# File 'lib/tapyrus/pstt/input.rb', line 259

def validate_utxo!
  raise Error, "PSTT_IN_UTXO is required." unless utxo
  raise Error, "The txid of PSTT_IN_UTXO does not match PSTT_IN_PREVIOUS_TXID." unless utxo.txid == out_point.txid
  raise Error, "PSTT_IN_OUTPUT_INDEX does not exist in PSTT_IN_UTXO." unless utxo.outputs[out_point.index]
end