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

@spec append(list() | nil, term()) :: list()

Appends an item to a list-valued field, treating nil as the empty list. Preserves insertion order.

Link to this function

append_unique(items, item, key_fun)

View Source
@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.

Link to this function

classify_unknown_record(key, value)

View Source
@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}.

Link to this function

combine_repeatable(list_a, list_b, key_fun)

View Source
@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.

Link to this function

combine_singleton(value, value)

View Source
@spec combine_singleton(term(), term()) ::
  {:ok, term()} | {: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.

Link to this function

ensure_public_xkey(xkey)

View Source
@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.

Link to this function

parse_compact_size_value(key_value)

View Source
@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.

Link to this function

parse_key_value(psbt, accumulator, parse_func)

View Source
@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.

Link to this function

serialize_key_origin(key_origin)

View Source
@spec serialize_key_origin(Bitcoinex.PSBT.KeyOrigin.t()) :: binary()

Serializes a key origin: the 4-byte fingerprint followed by little-endian uint32 derivation indexes.

Link to this function

serialize_kv(key, value)

View Source
@spec serialize_kv(binary(), binary()) :: binary()

Serializes a key-value record: compact-size key length, key, compact-size value length, value.

Link to this function

serialize_repeatable(items, serialize_func)

View Source
@spec serialize_repeatable(list() | nil, (term() -> binary())) :: binary()

Serializes a repeatable (list-valued) field, mapping each item through serialize_func. A nil field serializes to nothing.

Link to this function

serialize_xpub_keydata(xkey)

View Source
@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).

Link to this function

validate_key_origin(arg1)

View Source
@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.

Link to this function

validate_updater_key(arg1, arg2, known_types)

View Source
@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)).