defmodule BitcoinLib.Key.PublicKey.Serialization do @moduledoc """ Extended public key serialization module To test the results, use https://www.npmjs.com/package/@swan-bitcoin/xpub-cli Serialization types: https://github.com/satoshilabs/slips/blob/master/slip-0132.md """ @xpub_version_bytes 0x0488B21E @ypub_version_bytes 0x049D7CB2 @zpub_version_bytes 0x04B24746 @tpub_version_bytes 0x043587CF @upub_version_bytes 0x044A5262 @vpub_version_bytes 0x045F1CF6 alias BitcoinLib.Crypto alias BitcoinLib.Key.PublicKey @doc """ Serialization of a master public key into an exportable version ## Examples values from https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#test-vector-1 iex> %BitcoinLib.Key.PublicKey{ ...> key: <<0x339A36013301597DAEF41FBE593A02CC513D0B55527EC2DF1050E2E8FF49C85C2::264>>, ...> chain_code: <<0x873DFF81C02F525623FD1FE5167EAC3A55A049DE3D314BB42EE227FFED37D508::256>>, ...> depth: 0, ...> index: 0, ...> parent_fingerprint: <<0::32>> ...> } ...> |> BitcoinLib.Key.PublicKey.Serialization.serialize(:mainnet, :bip32) { :ok, "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" } """ @spec serialize(%PublicKey{}, :mainnet | :testnet, :bip32 | :bip49 | :bip84) :: {:ok, binary()} | {:error, binary()} def serialize(%PublicKey{} = pub_key, :mainnet, :bip32) do {:ok, execute(pub_key, @xpub_version_bytes)} end def serialize(%PublicKey{} = pub_key, :mainnet, :bip49) do {:ok, execute(pub_key, @ypub_version_bytes)} end def serialize(%PublicKey{} = pub_key, :mainnet, :bip84) do {:ok, execute(pub_key, @zpub_version_bytes)} end def serialize(%PublicKey{} = pub_key, :testnet, :bip32) do {:ok, execute(pub_key, @tpub_version_bytes)} end def serialize(%PublicKey{} = pub_key, :testnet, :bip49) do {:ok, execute(pub_key, @upub_version_bytes)} end def serialize(%PublicKey{} = pub_key, :testnet, :bip84) do {:ok, execute(pub_key, @vpub_version_bytes)} end def serialize(%PublicKey{}, _, _) do {:error, "unknown serialization format"} end @spec execute(%PublicKey{}, integer()) :: binary() defp execute( %PublicKey{ key: key, depth: depth, index: index, parent_fingerprint: parent_fingerprint, chain_code: chain_code }, version_bytes ) do data = << version_bytes::32, depth::8, parent_fingerprint::bitstring-32, index::32, chain_code::bitstring-256, key::bitstring-264 >> << data::bitstring, Crypto.checksum(data)::bitstring >> |> Base58.encode() end end