Module: Tapyrus::PSTT
- Defined in:
- lib/tapyrus/pstt.rb,
lib/tapyrus/pstt/tx.rb,
lib/tapyrus/pstt/input.rb,
lib/tapyrus/pstt/output.rb,
lib/tapyrus/pstt/proprietary.rb,
lib/tapyrus/pstt/key_origin_info.rb
Overview
Partially Signed Tapyrus Transaction (PSTT) defined by TIP-0174.
A PSTT holds a not-yet-signed Tapyrus transaction together with the metadata signers need. The data model follows BIP-370: the transaction is represented by per-input and per-output fields, so inputs and outputs can be added after creation.
Defined Under Namespace
Modules: GlobalTypes, InputTypes, OutputTypes, TxModifiable Classes: Error, Input, KeyOriginInfo, KeyValue, Output, Proprietary, Tx
Constant Summary collapse
- MAGIC =
The magic bytes which begin every PSTT. ASCII "pstt" followed by the separator 0xFF.
"70737474ff".htb
- VERSION =
The only version this TIP defines.
0- LOCKTIME_THRESHOLD =
The boundary between height-based and time-based locktime.
500_000_000- SIGHASH_BASE_TYPES =
The sighash types TIP-0174 defines: SIGHASH_ALL, SIGHASH_NONE and SIGHASH_SINGLE, each optionally combined with SIGHASH_ANYONECANPAY.
[SIGHASH_TYPE[:all], SIGHASH_TYPE[:none], SIGHASH_TYPE[:single]].freeze
- SIGHASH_TYPES =
(SIGHASH_BASE_TYPES + SIGHASH_BASE_TYPES.map { |t| t | SIGHASH_TYPE[:anyonecanpay] }).freeze
Class Method Summary collapse
-
.order_records(records, order) ⇒ Array[Tapyrus::PSTT::KeyValue]
Put
recordsback into the order the map was parsed in, so that parsing a PSTT and serializing it again yields the same bytes. -
.parse_field(name) ⇒ Object
Parse the value or the key data of a record with
block, and report any failure as a Tapyrus::PSTT::Error. -
.parse_map(buf) ⇒ Array[Tapyrus::PSTT::KeyValue]
Parse a single map, which is a sequence of key-value records terminated by 0x00.
-
.read_bytes(buf, size) ⇒ String
Read exactly
sizebytes frombuf. -
.read_compact_size(buf) ⇒ Integer
Read a compact size unsigned integer from
buf. -
.read_count(record) ⇒ Object
Read a compact size unsigned integer from the value of
record. -
.read_i32(record) ⇒ Object
Read a 32-bit little endian signed integer from the value of
record. -
.read_i64(record) ⇒ Object
Read a 64-bit little endian signed integer from the value of
record. -
.read_u32(record) ⇒ Object
Read a 32-bit little endian unsigned integer from the value of
record. -
.read_u8(record) ⇒ Object
Read an 8-bit unsigned integer from the value of
record. -
.serialize_map(records, order = nil) ⇒ String
Serialize
recordsas a map. -
.validate_amount!(amount) ⇒ Object
Check that
amountis within the range Tapyrus allows. -
.validate_empty_keydata!(record) ⇒ Object
Check that
recordhas no key data, as its field definition requires. -
.validate_features!(features) ⇒ Object
Check that
featuresis a positive value. -
.validate_height_locktime!(value) ⇒ Object
Check that
valueis a valid PSTT_IN_REQUIRED_HEIGHT_LOCKTIME. -
.validate_pubkey_keydata!(record) ⇒ Object
Check that the key data of
recordis a public key. -
.validate_sighash_type!(hash_type) ⇒ Object
Check that
hash_typeis one of the sighash types TIP-0174 defines. -
.validate_time_locktime!(value) ⇒ Object
Check that
valueis a valid PSTT_IN_REQUIRED_TIME_LOCKTIME. -
.validate_tx_modifiable!(flags) ⇒ Object
Check that
flagssets no reserved bit of PSTT_GLOBAL_TX_MODIFIABLE. -
.validate_value_size!(record, size) ⇒ Object
Check that the value of
recordhassizebytes. -
.validate_version!(version) ⇒ Object
Check that
versionis a version this implementation supports.
Class Method Details
.order_records(records, order) ⇒ Array[Tapyrus::PSTT::KeyValue]
Put records back into the order the map was parsed in, so that parsing a PSTT and
serializing it again yields the same bytes. TIP-0174 prescribes no order - either order is
a valid PSTT carrying the same records - but a byte-stable round trip lets a caller hash,
cache or diff a PSTT without normalizing it first. A record which was not in the parsed
map, such as a signature collected since, follows the records which were, in the order
this implementation generates them. A map which was not parsed is emitted in ascending
order of the complete key.
189 190 191 192 193 194 |
# File 'lib/tapyrus/pstt.rb', line 189 def order_records(records, order) return records.sort_by { |r| [r.type, r.keydata] } if order.nil? || order.empty? rank = {} order.each_with_index { |key, i| rank[key] ||= i } records.each_with_index.sort_by { |record, i| [rank.fetch(record.key, order.size + i), i] }.map(&:first) end |
.parse_field(name) ⇒ Object
Parse the value or the key data of a record with block, and report any failure as a
Tapyrus::PSTT::Error. The records of a PSTT come from another party, so a malformed one
must not surface as an exception of the parser the field happens to be built on.
267 268 269 270 271 272 273 |
# File 'lib/tapyrus/pstt.rb', line 267 def parse_field(name) yield rescue Error raise rescue StandardError => e raise Error, "#{name} is malformed. #{e.}" end |
.parse_map(buf) ⇒ Array[Tapyrus::PSTT::KeyValue]
Parse a single map, which is a sequence of key-value records terminated by 0x00.
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/tapyrus/pstt.rb', line 152 def parse_map(buf) records = [] keys = {} loop do raise Error, "PSTT map is not terminated." if buf.eof? key_len = read_compact_size(buf) break if key_len.zero? # the 0x00 separator key = read_bytes(buf, key_len) key_buf = StringIO.new(key) type = read_compact_size(key_buf) keydata = key_buf.read || "" raise Error, "PSTT map contains a duplicated key." if keys.key?(key) keys[key] = true value_len = read_compact_size(buf) records << KeyValue.new(type, keydata, read_bytes(buf, value_len)) end records end |
.read_bytes(buf, size) ⇒ String
Read exactly size bytes from buf.
142 143 144 145 146 |
# File 'lib/tapyrus/pstt.rb', line 142 def read_bytes(buf, size) data = buf.read(size) raise Error, "PSTT payload is truncated." if data.nil? || data.bytesize != size data end |
.read_compact_size(buf) ⇒ Integer
Read a compact size unsigned integer from buf.
Every compact size integer of the format must be minimally encoded. TIP-0174 states the
requirement for
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/tapyrus/pstt.rb', line 119 def read_compact_size(buf) prefix = read_bytes(buf, 1).unpack1("C") case prefix when 0xfd value = read_bytes(buf, 2).unpack1("v") raise Error, "compact size is not minimally encoded." if value < 0xfd when 0xfe value = read_bytes(buf, 4).unpack1("V") raise Error, "compact size is not minimally encoded." if value <= 0xffff when 0xff value = read_bytes(buf, 8).unpack1("Q<") raise Error, "compact size is not minimally encoded." if value <= 0xffffffff else value = prefix end value end |
.read_count(record) ⇒ Object
Read a compact size unsigned integer from the value of record.
255 256 257 258 259 260 |
# File 'lib/tapyrus/pstt.rb', line 255 def read_count(record) buf = StringIO.new(record.value) count = read_compact_size(buf) raise Error, format("value for type 0x%02x is not a compact size uint.", record.type) unless buf.eof? count end |
.read_i32(record) ⇒ Object
Read a 32-bit little endian signed integer from the value of record.
237 238 239 240 |
# File 'lib/tapyrus/pstt.rb', line 237 def read_i32(record) validate_value_size!(record, 4) record.value.unpack1("l<") end |
.read_i64(record) ⇒ Object
Read a 64-bit little endian signed integer from the value of record.
243 244 245 246 |
# File 'lib/tapyrus/pstt.rb', line 243 def read_i64(record) validate_value_size!(record, 8) record.value.unpack1("q<") end |
.read_u32(record) ⇒ Object
Read a 32-bit little endian unsigned integer from the value of record.
231 232 233 234 |
# File 'lib/tapyrus/pstt.rb', line 231 def read_u32(record) validate_value_size!(record, 4) record.value.unpack1("V") end |
.read_u8(record) ⇒ Object
Read an 8-bit unsigned integer from the value of record.
249 250 251 252 |
# File 'lib/tapyrus/pstt.rb', line 249 def read_u8(record) validate_value_size!(record, 1) record.value.unpack1("C") end |
.serialize_map(records, order = nil) ⇒ String
Serialize records as a map.
175 176 177 |
# File 'lib/tapyrus/pstt.rb', line 175 def serialize_map(records, order = nil) order_records(records, order).map(&:to_payload).join + "\x00".b end |
.validate_amount!(amount) ⇒ Object
Check that amount is within the range Tapyrus allows.
287 288 289 290 291 |
# File 'lib/tapyrus/pstt.rb', line 287 def validate_amount!(amount) unless amount.is_a?(Integer) && amount >= 0 && amount <= MAX_MONEY raise Error, "PSTT_OUT_AMOUNT must be between 0 and #{MAX_MONEY}, but #{amount}." end end |
.validate_empty_keydata!(record) ⇒ Object
Check that record has no key data, as its field definition requires.
199 200 201 |
# File 'lib/tapyrus/pstt.rb', line 199 def validate_empty_keydata!(record) raise Error, format("keydata for type 0x%02x must be empty.", record.type) unless record.keydata.empty? end |
.validate_features!(features) ⇒ Object
Check that features is a positive value. Tapyrus currently defines only 1, but a
transaction feature which a later version adds is accepted so that this implementation
can carry it through.
298 299 300 |
# File 'lib/tapyrus/pstt.rb', line 298 def validate_features!(features) raise Error, "PSTT_GLOBAL_TX_FEATURES must be greater than 0, but #{features}." unless features.positive? end |
.validate_height_locktime!(value) ⇒ Object
Check that value is a valid PSTT_IN_REQUIRED_HEIGHT_LOCKTIME.
328 329 330 331 332 |
# File 'lib/tapyrus/pstt.rb', line 328 def validate_height_locktime!(value) if value.zero? || value >= LOCKTIME_THRESHOLD raise Error, "PSTT_IN_REQUIRED_HEIGHT_LOCKTIME must be greater than 0 and less than #{LOCKTIME_THRESHOLD}." end end |
.validate_pubkey_keydata!(record) ⇒ Object
Check that the key data of record is a public key.
206 207 208 209 210 211 212 |
# File 'lib/tapyrus/pstt.rb', line 206 def validate_pubkey_keydata!(record) size = record.keydata.bytesize unless [Tapyrus::Key::COMPRESSED_PUBLIC_KEY_SIZE, Tapyrus::Key::PUBLIC_KEY_SIZE].include?(size) raise Error, format("keydata for type 0x%02x must be a 33- or 65-byte public key, but %d bytes.", record.type, size) end end |
.validate_sighash_type!(hash_type) ⇒ Object
Check that hash_type is one of the sighash types TIP-0174 defines.
278 279 280 281 282 |
# File 'lib/tapyrus/pstt.rb', line 278 def validate_sighash_type!(hash_type) unless SIGHASH_TYPES.include?(hash_type) raise Error, format("Sighash type 0x%x is not defined by TIP-0174.", hash_type) end end |
.validate_time_locktime!(value) ⇒ Object
Check that value is a valid PSTT_IN_REQUIRED_TIME_LOCKTIME.
320 321 322 323 324 |
# File 'lib/tapyrus/pstt.rb', line 320 def validate_time_locktime!(value) if value < LOCKTIME_THRESHOLD raise Error, "PSTT_IN_REQUIRED_TIME_LOCKTIME must be greater than or equal to #{LOCKTIME_THRESHOLD}." end end |
.validate_tx_modifiable!(flags) ⇒ Object
Check that flags sets no reserved bit of PSTT_GLOBAL_TX_MODIFIABLE.
305 306 307 308 309 |
# File 'lib/tapyrus/pstt.rb', line 305 def validate_tx_modifiable!(flags) unless (flags & ~TxModifiable::ALL).zero? raise Error, "The reserved bits of PSTT_GLOBAL_TX_MODIFIABLE must be 0." end end |
.validate_value_size!(record, size) ⇒ Object
Check that the value of record has size bytes.
218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/tapyrus/pstt.rb', line 218 def validate_value_size!(record, size) unless record.value.bytesize == size raise Error, format( "value for type 0x%02x must be %d bytes, but %d bytes.", record.type, size, record.value.bytesize ) end end |