View Source Bitcoinex.PSBT.Utils (bitcoinex v0.3.0)
Contains utility functions used throughout PSBT serialization.
Link to this section Summary
Functions
Appends an item to a list-valued field, treating nil as the empty list.
Preserves insertion order.
Appends a record to a repeatable (list-valued, nil-as-empty) field unless a
record with the same key (per key_fun) is already present — a PSBT map may
not contain duplicate keys, so an Updater must not create one.
Classifies an unrecognized key-value record: proprietary keys (leading byte
0xFC) are collected into the :proprietary field, all others into :unknown.
Returns {field_name, record} where record is %{key: key, value: value}.
Combines two repeatable (list-valued) fields into their union, identifying
records by key_fun. Two records that share a key but differ in value are a
{:error, :conflicting_field}.
Combines two values for a singleton (non-repeatable) field (BIP-174 Combiner):
takes whichever side is set, keeps the value if both agree, and returns
{:error, :conflicting_field} if both are set and differ.
Returns :ok for an extended public key, {:error, :private_key_not_allowed}
for an extended private key.
Reads a single compact-size-prefixed value off the front of a binary.
Parses a key origin value: a 4-byte master fingerprint (stored verbatim, not reinterpreted) followed by little-endian uint32 derivation indexes.
Parses a sequence of key-value records terminated by the 0x00 map separator,
dispatching each record to parse_func.
Parses the 78-byte raw extended key from a PSBT xpub key into an ExtendedKey.
The PSBT encoding omits the Base58 checksum that ExtendedKey expects, so it
is appended before parsing.
Serializes a key origin: the 4-byte fingerprint followed by little-endian uint32 derivation indexes.
Serializes a key-value record: compact-size key length, key, compact-size value length, value.
Serializes a repeatable (list-valued) field, mapping each item through
serialize_func. A nil field serializes to nothing.
Serializes an ExtendedKey to the 78-byte raw form used in PSBT xpub keys
(i.e. without the trailing 4-byte Base58 checksum).
Validates a caller-supplied KeyOrigin: the fingerprint must be exactly 4
bytes and every derivation index a concrete uint32 (DerivationPath
wildcards like :any cannot be serialized into a PSBT). Guards the Updater
against values that would corrupt or raise at encode time.
Validates the raw key of an Updater-supplied :proprietary or :unknown
record. A :proprietary key must carry the 0xFC type byte. An :unknown key
must be non-empty (an empty key re-serializes as the 0x00 map separator,
corrupting the PSBT) and must not use a type byte the section parses into a
dedicated field, nor 0xFC — such a record would re-decode as a different
struct (or a :duplicate_key error), breaking decode(encode_b64(psbt)).
Link to this section Functions
Appends an item to a list-valued field, treating nil as the empty list.
Preserves insertion order.
@spec append_unique(list() | nil, term(), (term() -> term())) :: {:ok, list()} | {:error, :duplicate_key}
Appends a record to a repeatable (list-valued, nil-as-empty) field unless a
record with the same key (per key_fun) is already present — a PSBT map may
not contain duplicate keys, so an Updater must not create one.
@spec classify_unknown_record(binary(), binary()) :: {:proprietary | :unknown, %{key: binary(), value: binary()}}
Classifies an unrecognized key-value record: proprietary keys (leading byte
0xFC) are collected into the :proprietary field, all others into :unknown.
Returns {field_name, record} where record is %{key: key, value: value}.
@spec combine_repeatable(list() | nil, list() | nil, (term() -> term())) :: {:ok, list() | nil} | {:error, :conflicting_field}
Combines two repeatable (list-valued) fields into their union, identifying
records by key_fun. Two records that share a key but differ in value are a
{:error, :conflicting_field}.
The union keeps the first list's records in their existing order and appends
only records new from the second list (Bitcoin Core's Merge semantics), so
combine(a, a) == a unconditionally and combining reproduces the official
BIP-174 Combiner vector byte-for-byte. Commutativity holds up to record
order — the same record set results either way — which is all BIP-174
asks of key-value maps. An empty union normalizes back to nil.
Combines two values for a singleton (non-repeatable) field (BIP-174 Combiner):
takes whichever side is set, keeps the value if both agree, and returns
{:error, :conflicting_field} if both are set and differ.
@spec ensure_public_xkey(Bitcoinex.ExtendedKey.t()) :: :ok | {:error, :private_key_not_allowed}
Returns :ok for an extended public key, {:error, :private_key_not_allowed}
for an extended private key.
@spec parse_compact_size_value(binary()) :: {:ok, binary(), binary()} | {:error, :non_canonical_compact_size}
Reads a single compact-size-prefixed value off the front of a binary.
Returns {:ok, value, remaining}. The success tuple is tagged so that it
cannot be confused with the error tuple: an untagged {value, remaining}
is shape-identical to {:error, reason}, so a caller that skipped the
error check would bind value = :error and carry on.
BIP-174 requires compact size uints to be minimally encoded, so a non-minimal length is rejected: it cannot survive a re-serialize byte-for-byte, and a non-minimally encoded zero key length would produce an empty key that re-serializes as a map separator, silently corrupting the PSBT on re-encode.
@spec parse_key_origin(binary()) :: {:ok, Bitcoinex.PSBT.KeyOrigin.t()} | {:error, :invalid_derivation}
Parses a key origin value: a 4-byte master fingerprint (stored verbatim, not reinterpreted) followed by little-endian uint32 derivation indexes.
@spec parse_key_value(binary(), struct(), function()) :: {:ok, {struct(), binary()}} | {:error, term()}
Parses a sequence of key-value records terminated by the 0x00 map separator,
dispatching each record to parse_func.
Rejects duplicate keys within the map (BIP-174) with {:error, :duplicate_key},
and propagates any {:error, reason} raised by parse_func.
Returns {:ok, {accumulator, remaining_binary}} on success.
@spec parse_xpub_keydata(binary()) :: {:ok, Bitcoinex.ExtendedKey.t()} | {:error, term()}
Parses the 78-byte raw extended key from a PSBT xpub key into an ExtendedKey.
The PSBT encoding omits the Base58 checksum that ExtendedKey expects, so it
is appended before parsing.
BIP-174 defines the key-data as an extended public key; an extended private
key is rejected with {:error, :private_key_not_allowed} — PSBTs are meant
to be shared, and no private key may pass through this module.
@spec serialize_key_origin(Bitcoinex.PSBT.KeyOrigin.t()) :: binary()
Serializes a key origin: the 4-byte fingerprint followed by little-endian uint32 derivation indexes.
Serializes a key-value record: compact-size key length, key, compact-size value length, value.
Serializes a repeatable (list-valued) field, mapping each item through
serialize_func. A nil field serializes to nothing.
@spec serialize_xpub_keydata(Bitcoinex.ExtendedKey.t()) :: binary()
Serializes an ExtendedKey to the 78-byte raw form used in PSBT xpub keys
(i.e. without the trailing 4-byte Base58 checksum).
@spec validate_key_origin(Bitcoinex.PSBT.KeyOrigin.t()) :: :ok | {:error, :invalid_key_origin}
Validates a caller-supplied KeyOrigin: the fingerprint must be exactly 4
bytes and every derivation index a concrete uint32 (DerivationPath
wildcards like :any cannot be serialized into a PSBT). Guards the Updater
against values that would corrupt or raise at encode time.
@spec validate_updater_key(:proprietary | :unknown, binary(), Enumerable.t()) :: :ok | {:error, :invalid_key_format}
Validates the raw key of an Updater-supplied :proprietary or :unknown
record. A :proprietary key must carry the 0xFC type byte. An :unknown key
must be non-empty (an empty key re-serializes as the 0x00 map separator,
corrupting the PSBT) and must not use a type byte the section parses into a
dedicated field, nor 0xFC — such a record would re-decode as a different
struct (or a :duplicate_key error), breaking decode(encode_b64(psbt)).