PSBT

O formato para passar transações para assinatura

Diagrama mostrando uma visão geral do formato de dados PSBT.

Uma PSBT (Partially Signed Bitcoin Transaction — transação de bitcoin parcialmente assinada) é um formato de dados para passar transações adiante para assinatura.

Esse formato contém, essencialmente, os dados brutos de uma transação não assinada, junto com os dados extras necessários para assinar as entradas.

Então, se você exportar uma transação não assinada da sua carteira de bitcoin, ela provavelmente estará no formato PSBT. A maioria das carteiras modernas suporta a exportação e a assinatura de PSBTs, por exemplo:

Decodificador de PSBT

Decodificador básico para o formato de dados PSBT (BIP 174 / BIP 370).

Dados da PSBT

0 bytes

Result

Propósito

Por que precisamos de PSBTs?

Diagrama mostrando uma PSBT sendo enviada para um dispositivo de hardware para ser assinada.

Para assinar uma transação de bitcoin, você na verdade precisa de algumas informações adicionais que não estão incluídas nos próprios dados brutos da transação não assinada.

Por exemplo, se você quer criar uma assinatura para destravar uma entrada P2WPKH, você precisa dos seguintes dados extras:

Essa informação fica armazenada na transação anterior que criou a saída (UTXO), mas não na transação atual que a gasta como entrada.

Ora, isso não é um problema se você está usando uma carteira com conexão à internet (ou seja, uma "hot wallet"), pois essa informação pode ser recuperada da blockchain ao procurar a transação anterior que criou a UTXO que você está gastando.

Porém, se você está usando uma carteira offline para assinar a sua transação (ex.: um dispositivo de hardware ou "cold wallet"), ela não terá uma cópia atualizada da blockchain. Então, mesmo que ela tenha a chave privada para assinar a entrada, ela não tem acesso à informação extra necessária para de fato criar a assinatura.

Portanto, o formato PSBT permite que você passe todos os dados necessários para assinar a transação em uma carteira/dispositivo separado.

Benefícios

Por que o formato PSBT foi criado?

Diagrama mostrando uma PSBT sendo usada para destravar uma entrada multisig. A PSBT é enviada para vários dispositivos para assinatura, antes de ser combinada em uma única PSBT e na transação final assinada.

Uma PSBT fornece um formato padrão para passar transações adiante para assinatura.

Antes das PSBTs, as carteiras implementavam o seu próprio formato para exportar os dados extras necessários para criar as assinaturas de uma transação. Isso funcionava, mas significava que o seu formato de transação não assinada provavelmente não era compatível com outras carteiras e dispositivos. Portanto, a pessoa que assinasse a PSBT precisaria usar o mesmo software que a pessoa que a criou.

Porém, com as PSBTs, uma transação não assinada pode ser exportada e assinada por diferentes carteiras e dispositivos.

Por exemplo, você poderia construir uma transação que gasta uma saída multisig (ex.: P2MS), exigindo assinaturas de 3 pessoas diferentes para destravá-la. Usando o formato PSBT, você pode enviar a cada signatário os dados da transação não assinada, e cada signatário pode usar a carteira/dispositivo que preferir para adicionar a sua assinatura, sem ter que se preocupar se o seu software entende o formato da transação não assinada.

Essas PSBTs separadas podem então ser combinadas para criar a transação final assinada.

Então, ter um formato comum para transações não assinadas (ou parcialmente assinadas) permite a compatibilidade entre diferentes carteiras e dispositivos de bitcoin.

Estrutura

Como é uma PSBT?

O formato PSBT é uma série de pares chave-valor contidos dentro de mapas separados.

Diagrama mostrando a estrutura de dados de uma PSBT.

Cabeçalho

Uma PSBT sempre começa com alguns bytes de cabeçalho para identificar os dados como uma PSBT:

Ícone Ferramenta ASCII

ASCII

Converta entre bytes (em hexadecimal) e caracteres ASCII.

0 bytes
Bytes
0 caracteres
  • Os bytes hex entre 0x20 e 0x7f contêm os caracteres imprimíveis.
  • Qualquer valor 0x1f ou abaixo é um caractere de controle (não será exibido, ou exibirá um caractere estranho).
  • Qualquer valor 0x80 ou acima não exibirá nada.

Veja o padrão de codificação ISO 646 para detalhes.

Mapas

Depois do cabeçalho, a PSBT contém três tipos de mapa (na seguinte ordem):

O fim de cada mapa é indicado por um separador 00 de 1 byte.

Os mapas de entrada e de saída precisam estar na ordem correspondente às entradas e saídas da transação não assinada. Cada entrada e saída precisa ter o seu próprio mapa. Se você não precisa incluir nenhum par chave-valor para uma entrada ou saída, basta usar o separador 00 para indicar o fim do mapa.

Você precisa extrair a contagem de entradas e a contagem de saídas da transação não assinada a partir do mapa global, para determinar o número de mapas de entrada e de saída na PSBT.

Chave-Valor

Dentro de cada mapa há uma série de pares chave-valor.

Cada par chave-valor tem a seguinte estrutura:

Esses pares chave-valor se repetem até o fim do mapa ser alcançado.

Código

Como decodificar e codificar uma PSBT

Aqui estão alguns trechos simples de código para decodificar e codificar PSBTs brutas.

Esses trechos de código não implementam todas as etapas necessárias para processar PSBTs (esse é o seu trabalho), nem detectam quaisquer erros, mas devem ser úteis para você pegar o jeito de desmontar e construir PSBTs para depuração e testes.

Decodificar

# ----
# PSBT
# ----

# psbt de exemplo em base64
base64 = "cHNidP8BAHcCAAAAAfdOPcmRewY2v88bQwUxucGsxSuXH0uEkKSNE80NzyiB
AAAAAAD9////AjU+AAAAAAAAGXapFD8wj0I2S+BXFbGa4wd7u8o36zxniKy2
WQAAAAAAABl2qRRjuoLSTMaSnO/tORKfAZa2RonLgYisf8kNAAABAL8CAAAA
AV4xZeLXJkrfKXk+fL6JBWhcMD/l0EPa78AWP1Pr4UpyAAAAAGpHMEQCIBS7
ZFI/kRlbC4oeDirf3uVy1EWPiNqSHhcwmkkiSwQ9AiAitN+1CQUHNCeY9dsc
ruF1U+B4Y+XtR5wXd83/YNy8ugEhAqAoKT0yVpnlt6sXX1Qz6eC2kO49L16M
A34ipmgeGZtl/f///wHNmAAAAAAAABl2qRSFdGbzr4xiIfOuLmEpSa4n414z
LYisf8kNACIGAxWaFXJLMzktDDpTbQAFCIllf+xM8nxjA86XolMXHogOGJwO
R5IsAACAAAAAgAAAAIAAAAAAAAAAAAAiAgKh3fjioSvCMeCiWW2YHs/jG7yf
7Ld4HzMyz/wF8fcKehicDkeSLAAAgAAAAIAAAACAAQAAAAAAAAAAAA=="

# converte base64 para string hex
require 'base64'
hex = Base64.decode64(base64).unpack("H*")[0]

# dica: se quiser decodificar a partir de hex em vez de base64, descomente a linha abaixo e coloque a string hex aqui
# hex = '70736274ff0100770200000001f74e3dc9917b0636bfcf1b430531b9c1acc52b971f4b8490a48d13cd0dcf28810000000000fdffffff02353e0000000000001976a9143f308f42364be05715b19ae3077bbbca37eb3c6788acb6590000000000001976a91463ba82d24cc6929cefed39129f0196b64689cb8188ac7fc90d00220603159a15724b33392d0c3a536d00050889657fec4cf27c6303ce97a253171e880e189c0e47922c0000800000008000000080000000000000000000220202a1ddf8e2a12bc231e0a2596d981ecfe31bbc9fecb7781f3332cffc05f1f70a7a189c0e47922c000080000000800000008001000000000000000000'

# mostra a PSBT em formato hex
puts hex
puts

# ---------
# Funções
# ---------
# Funções utilitárias para ajudar na decodificação

def read_compact_size(buffer)
  # lê o primeiro byte
  byte = buffer.read(1)

  # converte o byte em valor inteiro
  int = byte.unpack("C")[0] # C = inteiro [8 bits] (sem sinal)

  # obtém o valor contido no campo compact size
  if (int <= 0xFC)
    value = int # este byte
  elsif (int == 0xFD)
    value = buffer.read(2).unpack("v")[0] # próximos 2 bytes
  elsif (int == 0xFE)
    value = buffer.read(4).unpack("V")[0] # próximos 4 bytes
  elsif (int == 0xFF)
    value = buffer.read(8).unpack("Q")[0] # próximos 8 bytes
  end

  # retorna o valor
  return value
end

# ---------
# Tipos de Chave
# ---------
# Um array com todos os tipos de chave, útil para exibir o nome do tipo de chave

types = {
  'global' => {
    0 => 'PSBT_GLOBAL_UNSIGNED_TX',
    1 => 'PSBT_GLOBAL_XPUB',
    2 => 'PSBT_GLOBAL_TX_VERSION',
    3 => 'PSBT_GLOBAL_FALLBACK_LOCKTIME',
    4 => 'PSBT_GLOBAL_INPUT_COUNT',
    5 => 'PSBT_GLOBAL_OUTPUT_COUNT',
    6 => 'PSBT_GLOBAL_TX_MODIFIABLE',
    7 => 'PSBT_GLOBAL_SP_ECDH_SHARE',
    8 => 'PSBT_GLOBAL_SP_DLEQ',
    251 => 'PSBT_GLOBAL_VERSION',
    252 => 'PSBT_GLOBAL_PROPRIETARY'
  },
  'input' => {
    0 => 'PSBT_IN_NON_WITNESS_UTXO',
    1 => 'PSBT_IN_WITNESS_UTXO',
    2 => 'PSBT_IN_PARTIAL_SIG',
    3 => 'PSBT_IN_SIGHASH_TYPE',
    4 => 'PSBT_IN_REDEEM_SCRIPT',
    5 => 'PSBT_IN_WITNESS_SCRIPT',
    6 => 'PSBT_IN_BIP32_DERIVATION',
    7 => 'PSBT_IN_FINAL_SCRIPTSIG',
    8 => 'PSBT_IN_FINAL_SCRIPTWITNESS',
    9 => 'PSBT_IN_POR_COMMITMENT',
    10 => 'PSBT_IN_RIPEMD160',
    11 => 'PSBT_IN_SHA256',
    12 => 'PSBT_IN_HASH160',
    13 => 'PSBT_IN_HASH256',
    14 => 'PSBT_IN_PREVIOUS_TXID',
    15 => 'PSBT_IN_OUTPUT_INDEX',
    16 => 'PSBT_IN_SEQUENCE',
    17 => 'PSBT_IN_REQUIRED_TIME_LOCKTIME',
    18 => 'PSBT_IN_REQUIRED_HEIGHT_LOCKTIME',
    19 => 'PSBT_IN_TAP_KEY_SIG',
    20 => 'PSBT_IN_TAP_SCRIPT_SIG',
    21 => 'PSBT_IN_TAP_LEAF_SCRIPT',
    22 => 'PSBT_IN_TAP_BIP32_DERIVATION',
    23 => 'PSBT_IN_TAP_INTERNAL_KEY',
    24 => 'PSBT_IN_TAP_MERKLE_ROOT',
    26 => 'PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS',
    27 => 'PSBT_IN_MUSIG2_PUB_NONCE',
    28 => 'PSBT_IN_MUSIG2_PARTIAL_SIG',
    29 => 'PSBT_IN_SP_ECDH_SHARE',
    30 => 'PSBT_IN_SP_DLEQ',
    252 => 'PSBT_IN_PROPRIETARY'
  },
  'output' => {
    0 => 'PSBT_OUT_REDEEM_SCRIPT',
    1 => 'WPSBT_OUT_WITNESS_SCRIPT',
    2 => 'PSBT_OUT_BIP32_DERIVATION',
    3 => 'PSBT_OUT_AMOUNT',
    4 => 'PSBT_OUT_SCRIPT',
    5 => 'PSBT_OUT_TAP_INTERNAL_KEY',
    6 => 'PSBT_OUT_TAP_TREE',
    7 => 'PSBT_OUT_TAP_BIP32_DERIVATION',
    8 => 'PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS',
    9 => 'PSBT_OUT_SP_V0_INFO',
    10 => 'PSBT_OUT_SP_V0_LABEL',
    53 => 'PSBT_OUT_DNSSEC_PROOF',
    252 => 'PSBT_OUT_PROPRIETARY'
  }
}

# ------
# Decodificação
# ------
# Decodifica a PSBT

# estas variáveis serão atualizadas ao percorrer o mapa global, e são necessárias para conseguir ler o mapa de entrada e o mapa de saída seguintes
inputcount = 0
outputcount = 0

# converte a string hex da psbt em bytes
bytes = [hex].pack("H*")

# usa StringIO para ler os bytes conforme avançamos
require "stringio"
buffer = StringIO.new(bytes)

# Cabeçalho
magic_bytes = buffer.read(4)
separator   = buffer.read(1)

# Mapa Global
puts "GLOBAL MAP:"
puts
while true do

  # keylen
  keylen = read_compact_size(buffer)

  # se keylen for 0x00, chegamos ao byte separador e estamos no fim do mapa global
  if (keylen == 0)
    break
  end

  # usa keylen para ler keytype e keydata
  keytype_and_keydata = StringIO.new(buffer.read(keylen)) # cria outro StringIO a partir do buffer para podermos ler bytes dele

  # lê o keytype
  keytype = read_compact_size(keytype_and_keydata)

  # lê o keydata, que são todos os bytes restantes após o keytype
  keydata = keytype_and_keydata.read

  # valuelen
  valuelen = read_compact_size(buffer)

  # valuedata
  valuedata = buffer.read(valuelen)

  # se keytype = 0x00 (TX), extrai inputcount e outputcount dos dados brutos da transação fornecidos
  if (keytype == 0)

    # converte os dados brutos da transação em StringIO para podermos ler bytes dele
    tx = StringIO.new(valuedata)
    version = tx.read(4) # ignora

    # campo de contagem de entradas
    inputcount = read_compact_size(tx)

    # pula as entradas para chegar ao campo outputcount
    inputcount.times do
      txid_vout = tx.read(36)
      scriptsig_size = read_compact_size(tx)
      scriptsig = tx.read(scriptsig_size)
      sequence = tx.read(4)
    end

    # campo de contagem de saídas
    outputcount = read_compact_size(tx)
  end

  # se keytype = 0x04, o inputcount é fornecido explicitamente
  if (keytype == 4)
    inputcount = read_compact_size(StringIO.new(valuedata)) # converte para StringIO para ler o campo compact size
  end

  # se keytype = 0x05, o outputcount é fornecido explicitamente
  if (keytype == 5)
    outputcount = read_compact_size(StringIO.new(valuedata)) # converte para StringIO para ler o campo compact size
  end

  # mostra os dados do par chave-valor
  puts "  key:   #{keytype} (#{types['global'][keytype]}) #{keydata.unpack('H*')[0]}"
  puts "  value: #{valuedata.unpack('H*')[0]}"
  puts
end

# Mapa(s) de Entrada
inputcount.times do |i|
  # percorre cada entrada
  puts "INPUT #{i} MAP:"
  puts

  # obtém os pares chave-valor de cada entrada
  while true do

    # keylen
    keylen = read_compact_size(buffer)

    # se keylen for 0x00, chegamos ao byte separador e estamos no fim dos pares chave-valor desta entrada
    if (keylen == 0)
      break
    end

    # usa keylen para ler keytype e keydata
    keytype_and_keydata = StringIO.new(buffer.read(keylen)) # cria outro StringIO a partir do buffer para podermos ler bytes dele

    # lê o keytype
    keytype = read_compact_size(keytype_and_keydata)

    # lê o keydata, que são todos os bytes restantes após o keytype
    keydata = keytype_and_keydata.read

    # valuelen
    valuelen = read_compact_size(buffer)

    # valuedata
    valuedata = buffer.read(valuelen)

    # mostra os dados do par chave-valor
    puts "  key:   #{keytype} (#{types['input'][keytype]}) #{keydata.unpack('H*')[0]}"
    puts "  value: #{valuedata.unpack('H*')[0]}"
    puts
  end
end

# Mapa(s) de Saída
outputcount.times do |i|
  # percorre cada saída
  puts "OUTPUT #{i} MAP:"
  puts

  # obtém os pares chave-valor de cada saída
  while true do

    # keylen
    keylen = read_compact_size(buffer)

    # se keylen for 0x00, chegamos ao byte separador e estamos no fim dos pares chave-valor desta saída
    if (keylen == 0)
      break
    end

    # usa keylen para ler keytype e keydata
    keytype_and_keydata = StringIO.new(buffer.read(keylen)) # cria outro StringIO a partir do buffer para podermos ler bytes dele

    # lê o keytype
    keytype = read_compact_size(keytype_and_keydata)

    # lê o keydata, que são todos os bytes restantes após o keytype
    keydata = keytype_and_keydata.read

    # valuelen
    valuelen = read_compact_size(buffer)

    # valuedata
    valuedata = buffer.read(valuelen)

    # mostra os dados do par chave-valor
    puts "  key:   #{keytype} (#{types['output'][keytype]}) #{keydata.unpack('H*')[0]}"
    puts "  value: #{valuedata.unpack('H*')[0]}"
    puts
  end
end

Codificar

# ---------
# Constantes
# ---------

# Define constantes para os tipos de chave globais
PSBT_GLOBAL_UNSIGNED_TX = 0x00
PSBT_GLOBAL_XPUB = 0x01
PSBT_GLOBAL_TX_VERSION = 0x02
PSBT_GLOBAL_FALLBACK_LOCKTIME = 0x03
PSBT_GLOBAL_INPUT_COUNT = 0x04
PSBT_GLOBAL_OUTPUT_COUNT = 0x05
PSBT_GLOBAL_TX_MODIFIABLE = 0x06
PSBT_GLOBAL_SP_ECDH_SHARE = 0x07
PSBT_GLOBAL_SP_DLEQ = 0x08
PSBT_GLOBAL_VERSION = 0xFB
PSBT_GLOBAL_PROPRIETARY = 0xFC

# Define constantes para os tipos de chave de entrada
PSBT_IN_NON_WITNESS_UTXO = 0x00
PSBT_IN_WITNESS_UTXO = 0x01
PSBT_IN_PARTIAL_SIG = 0x02
PSBT_IN_SIGHASH_TYPE = 0x03
PSBT_IN_REDEEM_SCRIPT = 0x04
PSBT_IN_WITNESS_SCRIPT = 0x05
PSBT_IN_BIP32_DERIVATION = 0x06
PSBT_IN_FINAL_SCRIPTSIG = 0x07
PSBT_IN_FINAL_SCRIPTWITNESS = 0x08
PSBT_IN_POR_COMMITMENT = 0x09
PSBT_IN_RIPEMD160 = 0x0A
PSBT_IN_SHA256 = 0x0B
PSBT_IN_HASH160 = 0x0C
PSBT_IN_HASH256 = 0x0D
PSBT_IN_PREVIOUS_TXID = 0x0E
PSBT_IN_OUTPUT_INDEX = 0x0F
PSBT_IN_SEQUENCE = 0x10
PSBT_IN_REQUIRED_TIME_LOCKTIME = 0x11
PSBT_IN_REQUIRED_HEIGHT_LOCKTIME = 0x12
PSBT_IN_TAP_KEY_SIG = 0x13
PSBT_IN_TAP_SCRIPT_SIG = 0x14
PSBT_IN_TAP_LEAF_SCRIPT = 0x15
PSBT_IN_TAP_BIP32_DERIVATION = 0x16
PSBT_IN_TAP_INTERNAL_KEY = 0x17
PSBT_IN_TAP_MERKLE_ROOT = 0x18
PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS = 0x1A
PSBT_IN_MUSIG2_PUB_NONCE = 0x1B
PSBT_IN_MUSIG2_PARTIAL_SIG = 0x1C
PSBT_IN_SP_ECDH_SHARE = 0x1D
PSBT_IN_SP_DLEQ = 0x1E
PSBT_IN_PROPRIETARY = 0xFC

# Define constantes para os tipos de chave de saída
PSBT_OUT_REDEEM_SCRIPT = 0x00
PSBT_OUT_WITNESS_SCRIPT = 0x01
PSBT_OUT_BIP32_DERIVATION = 0x02
PSBT_OUT_AMOUNT = 0x03
PSBT_OUT_SCRIPT = 0x04
PSBT_OUT_TAP_INTERNAL_KEY = 0x05
PSBT_OUT_TAP_TREE = 0x06
PSBT_OUT_TAP_BIP32_DERIVATION = 0x07
PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS = 0x08
PSBT_OUT_SP_V0_INFO = 0x09
PSBT_OUT_SP_V0_LABEL = 0x0A
PSBT_OUT_DNSSEC_PROOF = 0x35
PSBT_OUT_PROPRIETARY = 0xFC

# ---------
# Funções
# ---------
# Funções utilitárias para ajudar na codificação

# Converte um inteiro em um campo compact size
def compact_size(i)
  if (                     i <= 252)                  then compactsize =        [i].pack("C").unpack("H*")[0]
  elsif (i > 252        && i <= 65535)                then compactsize = 'fd' + [i].pack("S<").unpack("H*")[0]
  elsif (i > 65535      && i <= 4294967295)           then compactsize = 'fe' + [i].pack("L<").unpack("H*")[0]
  elsif (i > 4294967295 && i <= 18446744073709551615) then compactsize = 'ff' + [i].pack("Q<").unpack("H*")[0]
  end

  return compactsize
end

# ----
# PSBT
# ----
# Constrói um array de mapas e pares chave-valor para converter em uma PSBT hex

psbt = {
  global: {
    0 => [
      {
        key: {type: PSBT_GLOBAL_UNSIGNED_TX, data: ''},
        value: '0200000001f74e3dc9917b0636bfcf1b430531b9c1acc52b971f4b8490a48d13cd0dcf28810000000000fdffffff02353e0000000000001976a9143f308f42364be05715b19ae3077bbbca37eb3c6788acb6590000000000001976a91463ba82d24cc6929cefed39129f0196b64689cb8188ac7fc90d00', # 1 entrada, 1 saída
      },
    ]
  },
  inputs: {
    0 => [
      {
        key: {type: PSBT_IN_NON_WITNESS_UTXO, data: ''},
        value: '02000000015e3165e2d7264adf29793e7cbe8905685c303fe5d043daefc0163f53ebe14a72000000006a473044022014bb64523f91195b0b8a1e0e2adfdee572d4458f88da921e17309a49224b043d022022b4dfb5090507342798f5db1caee17553e07863e5ed479c1777cdff60dcbcba012102a028293d325699e5b7ab175f5433e9e0b690ee3d2f5e8c037e22a6681e199b65fdffffff01cd980000000000001976a914857466f3af8c6221f3ae2e612949ae27e35e332d88ac7fc90d00',
      },
      {
        key: {type: PSBT_IN_BIP32_DERIVATION, data: '03159a15724b33392d0c3a536d00050889657fec4cf27c6303ce97a253171e880e'},
        value: '9c0e47922c00008000000080000000800000000000000000',
      },
    ],
  },
  outputs: {
    0 => [
      {
        key: {type: PSBT_OUT_BIP32_DERIVATION, data: '02a1ddf8e2a12bc231e0a2596d981ecfe31bbc9fecb7781f3332cffc05f1f70a7a'},
        value: '9c0e47922c00008000000080000000800100000000000000',
      },
    ],
    1 => [], # use um array vazio se um mapa de entrada/saída é esperado mas nenhum par chave-valor é necessário para ele
  },
}

# ------
# Codificação
# ------

# buffer
hex = ""

# cabeçalho
hex += "70736274ff" # magic bytes + separador

# percorre cada seção de mapa (global, input, output)
psbt.each do |mapname, maplist|

  # percorre os mapas individuais de cada seção (um para global, pode haver vários mapas de entrada e de saída)
  maplist.each do |map_index, map|

  # percorre todos os pares chave-valor de um mapa
  map.each do |keypair|

    # extrai os campos do array
    keytype = keypair[:key][:type] # inteiro (será preciso convertê-lo em um campo compact size)
    keydata = keypair[:key][:data] # bytes
    valuedata = keypair[:value] # bytes

    # codifica a chave
    keytype = compact_size(keytype)
    keydata = keydata
    keylen = compact_size((keytype.length / 2) + (keydata.length / 2)) # tamanho de keytype+keydata em bytes
    # nota: estamos trabalhando com _strings_ hex, então é preciso dividir o tamanho delas por 2 para obter o tamanho em _bytes_
    # nota: idealmente você deveria trabalhar com os dados em bytes crus em vez de strings hex

    # codifica o valor
    valuelen = compact_size(valuedata.length / 2) # tamanho de valuedata em bytes
    valuedata = valuedata

    # adiciona chave+valor ao buffer
    hex += keylen + keytype + keydata + valuelen + valuedata
  end # fim dos pares chave-valor

  # adiciona separador no fim de cada mapa
  hex += '00'

  end # fim do mapa
end

# ------
# Resultado
# ------

# mostra o array de dados original
require 'json'
puts JSON.pretty_generate(psbt) # codifica em json para imprimir o array original formatado
puts

# mostra a PSBT em hex
puts "hex:"
puts hex
puts

# mostra a PSBT em base64
require 'base64'
base64 = Base64.encode64([hex].pack('H*')) # é preciso converter a string hex em bytes crus antes de converter para base64
puts "base64:"
puts base64

A única parte complicada de decodificar PSBTs é garantir que você pegue o número correto de entradas e saídas do mapa global (via a chave PSBT_GLOBAL_UNSIGNED_TX, ou as chaves PSBT_GLOBAL_INPUT_COUNT e PSBT_GLOBAL_OUTPUT_COUNT). Isso é necessário para que você consiga determinar corretamente se os mapas seguintes são mapas de entrada ou de saída.

Certifique-se de interpretar o keytype como um campo compact size em vez de um único byte. Normalmente ele tem apenas 1 byte, mas tecnicamente é um campo compact size que pode se expandir para incluir mais tipos de chave no futuro. Então você quer levar isso em conta.

Tipos de Chave

Os tipos de dados em cada mapa

Cada mapa dentro de uma PSBT usa uma variedade de tipos de chave para armazenar dados.

Esses tipos de chave são representados por inteiros e indicam o tipo de dado armazenado nos campos de dados da chave e de dados do valor de cada par chave-valor.

Abaixo está uma lista de todos os diferentes tipos de chave disponíveis para cada mapa, junto com descrições dos dados armazenados.

As chaves dentro de cada mapa não precisam estar em ordem numérica. Porém, é útil colocá-las em ordem numérica, se possível, para ajudar a comparar a estrutura das PSBTs conforme elas são atualizadas durante o uso.

Os inteiros dos tipos de chave são específicos de cada tipo de mapa. Mapas diferentes podem usar os mesmos inteiros, mas eles representam tipos de chave diferentes dependendo do mapa em que você está. Portanto, você não consegue determinar o tipo de chave exato apenas pelo inteiro — você também precisa saber em qual mapa está usando-o.

As descrições abaixo são retiradas diretamente do BIP 174.

Mapa Global (11)

0 (0x00) Transação Não Assinada PSBT_GLOBAL_UNSIGNED_TX

v0: Obrigatório · v2: Não permitido

Estrutura da chave PSBT_GLOBAL_UNSIGNED_TX
CampoFormatoDescrição
Key Data(vazio)
Value Data<bytes transaction>A transação em serialização de rede. Os scriptSigs e as testemunhas de cada entrada precisam estar vazios. A transação precisa estar no formato de serialização antigo (sem testemunhas).
1 (0x01) Chave Pública Estendida PSBT_GLOBAL_XPUB
Estrutura da chave PSBT_GLOBAL_XPUB
CampoFormatoDescrição
Key Data<bytes xpub>A chave pública estendida serializada de 78 bytes, conforme definida pelo BIP 32. Chaves públicas estendidas são as que podem ser usadas para derivar as chaves públicas usadas nas entradas e saídas desta transação. Deve ser a chave pública no índice de derivação endurecido (hardened) mais alto, para que as chaves filhas não endurecidas usadas na transação possam ser derivadas.
Value Data<fingerprint de 4 bytes> <elemento de caminho uint little-endian de 32 bits>*O fingerprint da chave mestra conforme definido pelo BIP 32, concatenado com o caminho de derivação da chave pública. O caminho de derivação é representado como índices inteiros sem sinal little-endian de 32 bits, concatenados uns com os outros. O número de índices inteiros sem sinal de 32 bits precisa corresponder à profundidade fornecida na chave pública estendida.
2 (0x02) Versão da Transação PSBT_GLOBAL_TX_VERSION

v0: Não permitido · v2: Obrigatório

Estrutura da chave PSBT_GLOBAL_TX_VERSION
CampoFormatoDescrição
Key Data(vazio)
Value Data<int little-endian de 32 bits version>O inteiro com sinal little-endian de 32 bits representando o número de versão da transação sendo criada. Note que isso não é o mesmo que o número de versão da PSBT especificado pelo campo PSBT_GLOBAL_VERSION.
3 (0x03) Locktime de Fallback PSBT_GLOBAL_FALLBACK_LOCKTIME

v0: Não permitido

Estrutura da chave PSBT_GLOBAL_FALLBACK_LOCKTIME
CampoFormatoDescrição
Key Data(vazio)
Value Data<uint little-endian de 32 bits locktime>O inteiro sem sinal little-endian de 32 bits representando o locktime da transação a ser usado se nenhuma entrada especificar um locktime obrigatório.
4 (0x04) Contagem de Entradas PSBT_GLOBAL_INPUT_COUNT

v0: Não permitido · v2: Obrigatório

Estrutura da chave PSBT_GLOBAL_INPUT_COUNT
CampoFormatoDescrição
Key Data(vazio)
Value Data<compact size uint input count>Inteiro sem sinal compact size representando o número de entradas nesta PSBT.
5 (0x05) Contagem de Saídas PSBT_GLOBAL_OUTPUT_COUNT

v0: Não permitido · v2: Obrigatório

Estrutura da chave PSBT_GLOBAL_OUTPUT_COUNT
CampoFormatoDescrição
Key Data(vazio)
Value Data<compact size uint output count>Inteiro sem sinal compact size representando o número de saídas nesta PSBT.
6 (0x06) Flags de Modificação da Transação PSBT_GLOBAL_TX_MODIFIABLE

v0: Não permitido

Estrutura da chave PSBT_GLOBAL_TX_MODIFIABLE
CampoFormatoDescrição
Key Data(vazio)
Value Data<uint de 8 bits flags>Um inteiro sem sinal little-endian de 8 bits como um bit field para várias flags de modificação da transação. O Bit 0 é a Flag de Entradas Modificáveis e indica se as entradas podem ser modificadas. O Bit 1 é a Flag de Saídas Modificáveis e indica se as saídas podem ser modificadas. O Bit 2 é a flag Has SIGHASH_SINGLE e indica se a transação tem uma assinatura SIGHASH_SINGLE cujo emparelhamento de entrada e saída precisa ser preservado. O Bit 2 indica essencialmente que o Construtor precisa iterar as entradas para determinar se e como adicionar uma entrada.
7 (0x07) ECDH Share Global de Silent Payment PSBT_GLOBAL_SP_ECDH_SHARE

v0: Não permitido

Estrutura da chave PSBT_GLOBAL_SP_ECDH_SHARE
CampoFormatoDescrição
Key Data<scan key de 33 bytes>A scan key para a qual este ECDH share se destina.
Value Data<share de 33 bytes>Um ECDH share para uma scan key. O ECDH share é computado com a * B_scan, onde a é a soma de todas as chaves privadas de todas as entradas elegíveis, e B_scan é a scan key de um destinatário.
8 (0x08) Prova DLEQ Global de Silent Payment PSBT_GLOBAL_SP_DLEQ

v0: Não permitido

Estrutura da chave PSBT_GLOBAL_SP_DLEQ
CampoFormatoDescrição
Key Data<scan key de 33 bytes>A scan key que esta prova cobre.
Value Data<prova de 64 bytes>Uma prova DLEQ do BIP 374 computada para o ECDH share correspondente.
251 (0xFB) Número de Versão da PSBT PSBT_GLOBAL_VERSION
Estrutura da chave PSBT_GLOBAL_VERSION
CampoFormatoDescrição
Key Data(vazio)
Value Data<uint little-endian de 32 bits version>O inteiro sem sinal little-endian de 32 bits representando o número de versão desta PSBT. Se omitido, o número de versão é 0.
252 (0xFC) Tipo de Uso Proprietário PSBT_GLOBAL_PROPRIETARY
Estrutura da chave PSBT_GLOBAL_PROPRIETARY
CampoFormatoDescrição
Key Data<compact size uint identifier length> <bytes identifier> <compact size uint subtype> <bytes subkeydata>Inteiro sem sinal compact size do comprimento do identificador, seguido do prefixo identificador, seguido de um subtype inteiro sem sinal compact size, seguido dos próprios dados da chave.
Value Data<bytes data>Quaisquer dados de valor conforme definidos pelo usuário do tipo proprietário.

Mapa de Entrada (31)

0 (0x00) UTXO Não-Testemunha PSBT_IN_NON_WITNESS_UTXO
Estrutura da chave PSBT_IN_NON_WITNESS_UTXO
CampoFormatoDescrição
Key Data(vazio)
Value Data<bytes transaction>A transação, em formato de serialização de rede, da qual a entrada atual gasta. Deve estar presente para entradas que gastam saídas não-segwit e pode estar presente para entradas que gastam saídas segwit. Uma entrada pode ter tanto PSBT_IN_NON_WITNESS_UTXO quanto PSBT_IN_WITNESS_UTXO.
1 (0x01) UTXO de Testemunha PSBT_IN_WITNESS_UTXO
Estrutura da chave PSBT_IN_WITNESS_UTXO
CampoFormatoDescrição
Key Data(vazio)
Value Data<int little-endian de 64 bits amount> <compact size uint scriptPubKeylen> <bytes scriptPubKey>A saída inteira da transação, em serialização de rede, da qual a entrada atual gasta. Só deve estar presente para entradas que gastam saídas segwit, incluindo as embutidas em P2SH. Uma entrada pode ter tanto PSBT_IN_NON_WITNESS_UTXO quanto PSBT_IN_WITNESS_UTXO.
2 (0x02) Assinatura Parcial PSBT_IN_PARTIAL_SIG
Estrutura da chave PSBT_IN_PARTIAL_SIG
CampoFormatoDescrição
Key Data<bytes pubkey>A chave pública que corresponde a esta assinatura.
Value Data<bytes signature>A assinatura como seria empilhada a partir de um scriptSig ou de uma testemunha. A assinatura deve ser uma assinatura ECDSA válida correspondente à pubkey, que retornaria verdadeiro quando verificada, e não um valor que retornaria falso ou seria inválido de outra forma (como um NULLDUMMY).
3 (0x03) Tipo de Sighash PSBT_IN_SIGHASH_TYPE
Estrutura da chave PSBT_IN_SIGHASH_TYPE
CampoFormatoDescrição
Key Data(vazio)
Value Data<uint little-endian de 32 bits sighash type>O inteiro sem sinal de 32 bits especificando o tipo de sighash a ser usado para esta entrada. As assinaturas para esta entrada precisam usar o tipo de sighash; os finalizadores precisam falhar ao finalizar entradas que tenham assinaturas que não correspondam ao tipo de sighash especificado. Signatários que não conseguem produzir assinaturas com o tipo de sighash não devem fornecer uma assinatura.
4 (0x04) Redeem Script PSBT_IN_REDEEM_SCRIPT
Estrutura da chave PSBT_IN_REDEEM_SCRIPT
CampoFormatoDescrição
Key Data(vazio)
Value Data<bytes redeemScript>O redeemScript para esta entrada, se ela tiver um.
5 (0x05) Witness Script PSBT_IN_WITNESS_SCRIPT
Estrutura da chave PSBT_IN_WITNESS_SCRIPT
CampoFormatoDescrição
Key Data(vazio)
Value Data<bytes witnessScript>O witnessScript para esta entrada, se ela tiver um.
6 (0x06) Caminho de Derivação BIP 32 PSBT_IN_BIP32_DERIVATION
Estrutura da chave PSBT_IN_BIP32_DERIVATION
CampoFormatoDescrição
Key Data<bytes pubkey>A chave pública.
Value Data<fingerprint de 4 bytes> <elemento de caminho uint little-endian de 32 bits>*O fingerprint da chave mestra conforme definido pelo BIP 32, concatenado com o caminho de derivação da chave pública. O caminho de derivação é representado como índices inteiros sem sinal de 32 bits concatenados uns com os outros. As chaves públicas são as que serão necessárias para assinar esta entrada.
7 (0x07) scriptSig Finalizado PSBT_IN_FINAL_SCRIPTSIG
Estrutura da chave PSBT_IN_FINAL_SCRIPTSIG
CampoFormatoDescrição
Key Data(vazio)
Value Data<bytes scriptSig>O scriptSig Finalizado contém um scriptSig totalmente construído, com assinaturas e quaisquer outros scripts necessários para que a entrada passe na validação.
8 (0x08) scriptWitness Finalizado PSBT_IN_FINAL_SCRIPTWITNESS
Estrutura da chave PSBT_IN_FINAL_SCRIPTWITNESS
CampoFormatoDescrição
Key Data(vazio)
Value Data<bytes scriptWitness>O scriptWitness Finalizado contém uma testemunha totalmente construída, com assinaturas e quaisquer outros scripts necessários para que a entrada passe na validação.
9 (0x09) Compromisso de Prova de Reservas PSBT_IN_POR_COMMITMENT
Estrutura da chave PSBT_IN_POR_COMMITMENT
CampoFormatoDescrição
Key Data(vazio)
Value Data<bytes porCommitment>A string de mensagem de compromisso codificada em UTF-8 para a prova de reservas (proof-of-reserves). Veja o BIP 127 para mais informações.
10 (0x0A) Pré-imagem RIPEMD160 PSBT_IN_RIPEMD160
Estrutura da chave PSBT_IN_RIPEMD160
CampoFormatoDescrição
Key Data<hash de 20 bytes>O hash resultante da pré-imagem.
Value Data<bytes preimage>A pré-imagem do hash, codificada como um vetor de bytes, que precisa ser igual à chave quando passada pelo algoritmo RIPEMD160.
11 (0x0B) Pré-imagem SHA256 PSBT_IN_SHA256
Estrutura da chave PSBT_IN_SHA256
CampoFormatoDescrição
Key Data<hash de 32 bytes>O hash resultante da pré-imagem.
Value Data<bytes preimage>A pré-imagem do hash, codificada como um vetor de bytes, que precisa ser igual à chave quando passada pelo algoritmo SHA256.
12 (0x0C) Pré-imagem HASH160 PSBT_IN_HASH160
Estrutura da chave PSBT_IN_HASH160
CampoFormatoDescrição
Key Data<hash de 20 bytes>O hash resultante da pré-imagem.
Value Data<bytes preimage>A pré-imagem do hash, codificada como um vetor de bytes, que precisa ser igual à chave quando passada pelo algoritmo SHA256 seguido do algoritmo RIPEMD160 (HASH160).
13 (0x0D) Pré-imagem HASH256 PSBT_IN_HASH256
Estrutura da chave PSBT_IN_HASH256
CampoFormatoDescrição
Key Data<hash de 32 bytes>O hash resultante da pré-imagem.
Value Data<bytes preimage>A pré-imagem do hash, codificada como um vetor de bytes, que precisa ser igual à chave quando passada duas vezes pelo algoritmo SHA256 (HASH256).
14 (0x0E) TXID Anterior PSBT_IN_PREVIOUS_TXID

v0: Não permitido · v2: Obrigatório

Estrutura da chave PSBT_IN_PREVIOUS_TXID
CampoFormatoDescrição
Key Data(vazio)
Value Data<txid de 32 bytes>O txid de 32 bytes da transação anterior cuja saída em PSBT_IN_OUTPUT_INDEX está sendo gasta.
15 (0x0F) Índice da Saída Gasta PSBT_IN_OUTPUT_INDEX

v0: Não permitido · v2: Obrigatório

Estrutura da chave PSBT_IN_OUTPUT_INDEX
CampoFormatoDescrição
Key Data(vazio)
Value Data<uint little-endian de 32 bits index>Inteiro little-endian de 32 bits representando o índice da saída sendo gasta na transação com o txid de PSBT_IN_PREVIOUS_TXID.
16 (0x10) Número de Sequence PSBT_IN_SEQUENCE

v0: Não permitido

Estrutura da chave PSBT_IN_SEQUENCE
CampoFormatoDescrição
Key Data(vazio)
Value Data<uint little-endian de 32 bits sequence>O inteiro sem sinal little-endian de 32 bits para o número de sequence desta entrada. Se omitido, presume-se que o número de sequence seja o número de sequence final (0xffffffff).
17 (0x11) Locktime Obrigatório por Tempo PSBT_IN_REQUIRED_TIME_LOCKTIME

v0: Não permitido

Estrutura da chave PSBT_IN_REQUIRED_TIME_LOCKTIME
CampoFormatoDescrição
Key Data(vazio)
Value Data<uint little-endian de 32 bits locktime>Inteiro sem sinal little-endian de 32 bits maior ou igual a 500000000 representando o timestamp Unix mínimo que esta entrada exige que seja definido como o locktime da transação.
18 (0x12) Locktime Obrigatório por Altura PSBT_IN_REQUIRED_HEIGHT_LOCKTIME

v0: Não permitido

Estrutura da chave PSBT_IN_REQUIRED_HEIGHT_LOCKTIME
CampoFormatoDescrição
Key Data(vazio)
Value Data<uint de 32 bits locktime>Inteiro sem sinal little-endian de 32 bits menor que 500000000 representando a altura de bloco mínima que esta entrada exige que seja definida como o locktime da transação.
19 (0x13) Assinatura de Gasto por Chave Taproot PSBT_IN_TAP_KEY_SIG
Estrutura da chave PSBT_IN_TAP_KEY_SIG
CampoFormatoDescrição
Key Data(vazio)
Value Data<assinatura de 64 ou 65 bytes>A assinatura Schnorr de 64 ou 65 bytes para o gasto pelo caminho da chave de uma saída Taproot. Os finalizadores devem remover este campo após o PSBT_IN_FINAL_SCRIPTWITNESS ser construído.
20 (0x14) Assinatura de Gasto por Script Taproot PSBT_IN_TAP_SCRIPT_SIG
Estrutura da chave PSBT_IN_TAP_SCRIPT_SIG
CampoFormatoDescrição
Key Data<xonlypubkey de 32 bytes> <leafhash>Uma chave pública X-only de 32 bytes envolvida em um leaf script, concatenada com o hash de 32 bytes da folha (leaf) da qual faz parte.
Value Data<assinatura de 64 ou 65 bytes>A assinatura Schnorr de 64 ou 65 bytes para esta combinação de pubkey e folha. Os finalizadores devem remover este campo após o PSBT_IN_FINAL_SCRIPTWITNESS ser construído.
21 (0x15) Leaf Script Taproot PSBT_IN_TAP_LEAF_SCRIPT
Estrutura da chave PSBT_IN_TAP_LEAF_SCRIPT
CampoFormatoDescrição
Key Data<bytes control block>O control block para esta folha, conforme especificado no BIP 341. O control block contém o caminho da árvore de Merkle até esta folha.
Value Data<bytes script> <uint de 8 bits leaf version>O script desta folha como seria fornecido na pilha de testemunha, seguido do único byte da versão da folha. Note que as folhas incluídas neste campo devem ser aquelas que os signatários desta entrada são esperados de conseguir assinar. Os finalizadores devem remover este campo após o PSBT_IN_FINAL_SCRIPTWITNESS ser construído.
22 (0x16) Caminho de Derivação BIP 32 de Chave Taproot PSBT_IN_TAP_BIP32_DERIVATION
Estrutura da chave PSBT_IN_TAP_BIP32_DERIVATION
CampoFormatoDescrição
Key Data<xonlypubkey de 32 bytes>Uma chave pública X-only de 32 bytes envolvida nesta entrada. Pode ser a chave de saída, a chave interna ou uma chave presente em um leaf script.
Value Data<compact size uint number of hashes> <leaf hash de 32 bytes>* <fingerprint de 4 bytes> <elemento de caminho uint little-endian de 32 bits>*Um inteiro sem sinal compact size representando o número de leaf hashes, seguido de uma lista de leaf hashes, seguida do fingerprint de 4 bytes da chave mestra concatenado com o caminho de derivação da chave pública. O caminho de derivação é representado como índices inteiros sem sinal little-endian de 32 bits concatenados uns com os outros. As chaves públicas são as necessárias para gastar esta saída. Os leaf hashes são das folhas que envolvem esta chave pública. A chave interna não tem leaf hashes, então pode ser indicada com um comprimento de hashes 0. Os finalizadores devem remover este campo após o PSBT_IN_FINAL_SCRIPTWITNESS ser construído.
23 (0x17) Chave Interna Taproot PSBT_IN_TAP_INTERNAL_KEY
Estrutura da chave PSBT_IN_TAP_INTERNAL_KEY
CampoFormatoDescrição
Key Data(vazio)
Value Data<xonlypubkey de 32 bytes>A pubkey X-only usada como a chave interna nesta saída. Os finalizadores devem remover este campo após o PSBT_IN_FINAL_SCRIPTWITNESS ser construído.
24 (0x18) Raiz de Merkle Taproot PSBT_IN_TAP_MERKLE_ROOT
Estrutura da chave PSBT_IN_TAP_MERKLE_ROOT
CampoFormatoDescrição
Key Data(vazio)
Value Data<hash de 32 bytes>O hash da raiz de Merkle de 32 bytes. Os finalizadores devem remover este campo após o PSBT_IN_FINAL_SCRIPTWITNESS ser construído.
26 (0x1A) Chaves Públicas dos Participantes MuSig2 PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS
Estrutura da chave PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS
CampoFormatoDescrição
Key Data<plain aggregate pubkey de 33 bytes>A chave pública simples agregada MuSig2 do algoritmo KeyAgg. Essa chave pode ou não estar diretamente no script (como x-only). Em vez disso, pode ser uma chave pública pai a partir da qual as chaves públicas no script foram derivadas.
Value Data<compressed pubkey de 33 bytes>*Uma lista das chaves públicas comprimidas dos participantes na chave agregada MuSig2, na ordem exigida para a agregação. Se houve ordenação, então as chaves precisam estar na ordem ordenada.
27 (0x1B) Nonce Público MuSig2 PSBT_IN_MUSIG2_PUB_NONCE
Estrutura da chave PSBT_IN_MUSIG2_PUB_NONCE
CampoFormatoDescrição
Key Data<compressed pubkey de 33 bytes> <plain pubkey de 33 bytes> <hash de 32 bytes ou omitido>A chave pública comprimida do participante que fornece este nonce, seguida da chave pública simples para a qual o participante está fornecendo o nonce, seguida do hash de tapleaf do BIP 341 do leaf script Taproot que será assinado. Se a chave agregada for a chave interna do taproot ou a chave de saída do taproot, então o hash de tapleaf precisa ser omitido. A chave pública simples precisa ser a chave encontrada no script, e não a chave pública agregada da qual foi derivada, se foi derivada de uma chave agregada.
Value Data<public nonce de 66 bytes>O nonce público produzido pelo algoritmo NonceGen.
28 (0x1C) Assinatura Parcial do Participante MuSig2 PSBT_IN_MUSIG2_PARTIAL_SIG
Estrutura da chave PSBT_IN_MUSIG2_PARTIAL_SIG
CampoFormatoDescrição
Key Data<compressed pubkey de 33 bytes> <plain pubkey de 33 bytes> <hash de 32 bytes ou omitido>A chave pública comprimida do participante que fornece esta assinatura parcial, seguida da chave pública simples para a qual o participante está fornecendo a assinatura, seguida do hash de tapleaf do BIP 341 do leaf script Taproot que será assinado. Se a chave agregada for a chave interna ou de saída do taproot, então o hash de tapleaf precisa ser omitido. Note que a chave pública simples precisa ser a chave encontrada no script, e não a chave pública agregada da qual foi derivada.
Value Data<partial signature de 32 bytes>A assinatura parcial produzida pelo algoritmo Sign.
29 (0x1D) ECDH Share de Entrada de Silent Payment PSBT_IN_SP_ECDH_SHARE

v0: Não permitido

Estrutura da chave PSBT_IN_SP_ECDH_SHARE
CampoFormatoDescrição
Key Data<scan key de 33 bytes>A scan key para a qual este ECDH share se destina.
Value Data<share de 33 bytes>Um ECDH share para uma scan key. O ECDH share é computado com a * B_scan, onde a é a chave privada da chave pública do prevout correspondente, e B_scan é a scan key de um destinatário.
30 (0x1E) Prova DLEQ de Entrada de Silent Payment PSBT_IN_SP_DLEQ

v0: Não permitido

Estrutura da chave PSBT_IN_SP_DLEQ
CampoFormatoDescrição
Key Data<scan key de 33 bytes>A scan key que esta prova cobre.
Value Data<prova de 64 bytes>Uma prova DLEQ do BIP 374 computada para o ECDH share correspondente.
252 (0xFC) Tipo de Uso Proprietário PSBT_IN_PROPRIETARY
Estrutura da chave PSBT_IN_PROPRIETARY
CampoFormatoDescrição
Key Data<compact size uint identifier length> <bytes identifier> <compact size uint subtype> <bytes subkeydata>Inteiro sem sinal compact size do comprimento do identificador, seguido do prefixo identificador, seguido de um subtype inteiro sem sinal compact size, seguido dos próprios dados da chave.
Value Data<bytes data>Quaisquer dados de valor conforme definidos pelo usuário do tipo proprietário.

Mapa de Saída (13)

0 (0x00) Redeem Script PSBT_OUT_REDEEM_SCRIPT
Estrutura da chave PSBT_OUT_REDEEM_SCRIPT
CampoFormatoDescrição
Key Data(vazio)
Value Data<bytes redeemScript>O redeemScript para esta saída, se ela tiver um.
1 (0x01) Witness Script PSBT_OUT_WITNESS_SCRIPT
Estrutura da chave PSBT_OUT_WITNESS_SCRIPT
CampoFormatoDescrição
Key Data(vazio)
Value Data<bytes witnessScript>O witnessScript para esta saída, se ela tiver um.
2 (0x02) Caminho de Derivação BIP 32 PSBT_OUT_BIP32_DERIVATION
Estrutura da chave PSBT_OUT_BIP32_DERIVATION
CampoFormatoDescrição
Key Data<bytes public key>A chave pública.
Value Data<fingerprint de 4 bytes> <elemento de caminho uint little-endian de 32 bits>*O fingerprint da chave mestra concatenado com o caminho de derivação da chave pública. O caminho de derivação é representado como índices inteiros sem sinal little-endian de 32 bits concatenados uns com os outros. As chaves públicas são as necessárias para gastar esta saída.
3 (0x03) Valor da Saída PSBT_OUT_AMOUNT

v0: Não permitido · v2: Obrigatório

Estrutura da chave PSBT_OUT_AMOUNT
CampoFormatoDescrição
Key Data(vazio)
Value Data<int de 64 bits amount>Inteiro com sinal little-endian de 64 bits representando o valor da saída em satoshis.
4 (0x04) Script da Saída PSBT_OUT_SCRIPT

v0: Não permitido

Estrutura da chave PSBT_OUT_SCRIPT
CampoFormatoDescrição
Key Data(vazio)
Value Data<bytes script>O script para esta saída, também conhecido como scriptPubKey. Precisa ser omitido na PSBTv0. Precisa ser fornecido na PSBTv2 se não estiver enviando para um endereço de silent payment do BIP 352; caso contrário, pode ser omitido.
5 (0x05) Chave Interna Taproot PSBT_OUT_TAP_INTERNAL_KEY
Estrutura da chave PSBT_OUT_TAP_INTERNAL_KEY
CampoFormatoDescrição
Key Data(vazio)
Value Data<xonlypubkey de 32 bytes>A pubkey X-only usada como a chave interna nesta saída.
6 (0x06) Árvore Taproot PSBT_OUT_TAP_TREE
Estrutura da chave PSBT_OUT_TAP_TREE
CampoFormatoDescrição
Key Data(vazio)
Value Data{<uint de 8 bits depth> <uint de 8 bits leaf version> <compact size uint scriptlen> <bytes script>}*Uma ou mais tuplas representando a profundidade, a versão da folha e o script de uma folha na árvore Taproot, permitindo que a árvore inteira seja reconstruída. As tuplas precisam estar em ordem de busca em profundidade (depth-first), para que a árvore seja reconstruída corretamente. Cada tupla é um inteiro sem sinal de 8 bits representando a profundidade na árvore Taproot para este script, um inteiro sem sinal de 8 bits representando a versão da folha, o comprimento do script como um inteiro sem sinal compact size, e o próprio script.
7 (0x07) Caminho de Derivação BIP 32 de Chave Taproot PSBT_OUT_TAP_BIP32_DERIVATION
Estrutura da chave PSBT_OUT_TAP_BIP32_DERIVATION
CampoFormatoDescrição
Key Data<xonlypubkey de 32 bytes>Uma chave pública X-only de 32 bytes envolvida nesta saída. Pode ser a chave de saída, a chave interna ou uma chave presente em um leaf script.
Value Data<compact size uint number of hashes> <leaf hash de 32 bytes>* <fingerprint de 4 bytes> <elemento de caminho uint little-endian de 32 bits>*Um inteiro sem sinal compact size representando o número de leaf hashes, seguido de uma lista de leaf hashes, seguida do fingerprint de 4 bytes da chave mestra concatenado com o caminho de derivação da chave pública. O caminho de derivação é representado como índices inteiros sem sinal little-endian de 32 bits concatenados uns com os outros. As chaves públicas são as necessárias para gastar esta saída. Os leaf hashes são das folhas que envolvem esta chave pública. A chave interna não tem leaf hashes, então pode ser indicada com um comprimento de hashes 0. Os finalizadores devem remover este campo após o PSBT_IN_FINAL_SCRIPTWITNESS ser construído.
8 (0x08) Chaves Públicas dos Participantes MuSig2 PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS
Estrutura da chave PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS
CampoFormatoDescrição
Key Data<plain aggregate pubkey de 33 bytes>A chave pública simples agregada MuSig2 do algoritmo KeyAgg. Essa chave pode ou não estar diretamente no script. Em vez disso, pode ser uma chave pública pai a partir da qual as chaves públicas no script foram derivadas.
Value Data<compressed pubkey de 33 bytes>*Uma lista das chaves públicas comprimidas dos participantes na chave agregada MuSig2, na ordem exigida para a agregação. Se houve ordenação, então as chaves precisam estar na ordem ordenada.
9 (0x09) Dados de Silent Payment PSBT_OUT_SP_V0_INFO

v0: Não permitido

Estrutura da chave PSBT_OUT_SP_V0_INFO
CampoFormatoDescrição
Key Data(vazio)
Value Data<scan key de 33 bytes> <spend key de 33 bytes>As chaves públicas de scan e de spend do endereço de silent payments.
10 (0x0A) Rótulo de Silent Payment PSBT_OUT_SP_V0_LABEL

v0: Não permitido

Estrutura da chave PSBT_OUT_SP_V0_LABEL
CampoFormatoDescrição
Key Data(vazio)
Value Data<uint little-endian de 32 bits label>O rótulo a ser usado para computar a spend key do endereço de silent payments para verificar o troco.
53 (0x35) Prova DNSSEC do BIP 353 PSBT_OUT_DNSSEC_PROOF
Estrutura da chave PSBT_OUT_DNSSEC_PROOF
CampoFormatoDescrição
Key Data(vazio)
Value Data<BIP 353 human-readable name prefixado por 1 byte de comprimento><AuthenticationChain DNSSEC Proof no formato RFC 9102>Um nome legível por humanos do BIP 353 (sem o prefixo ₿), prefixado por 1 byte de comprimento. Seguido de uma AuthenticationChain DNSSEC do RFC 9102 (ou seja, uma série de Resource Records de DNS sem ordem específica) fornecendo uma prova DNSSEC para um registro TXT de DNS do BIP 353.
252 (0xFC) Tipo de Uso Proprietário PSBT_OUT_PROPRIETARY
Estrutura da chave PSBT_OUT_PROPRIETARY
CampoFormatoDescrição
Key Data<compact size uint identifier length> <bytes identifier> <compact size uint subtype> <bytes subkeydata>Inteiro sem sinal compact size do comprimento do identificador, seguido do prefixo identificador, seguido de um subtype inteiro sem sinal compact size, seguido dos próprios dados da chave.
Value Data<bytes data>Quaisquer dados de valor conforme definidos pelo usuário do tipo proprietário.

Uso

Como você usa as PSBTs?

Uma PSBT é processada por meio de vários passos antes de ser convertida na transação final assinada.

Você pensaria que seria apenas um processo de 2 passos, assim:

  1. Criar — Criar a PSBT e adicionar todos os dados extras necessários para a assinatura.
  2. Assinar — Assinar as entradas usando os dados extras e retornar a transação assinada.

Porém, esses dois passos podem ser desmembrados em passos adicionais (ou "papéis"), em que cada papel tem uma função específica para atualizar o estado da PSBT:

  1. Criador (Creator) — Criar a PSBT inicial com os dados da transação não assinada.
    • Construtor (Constructor) — Um papel adicional para PSBTs v2, usado para adicionar entradas e saídas aos dados da transação não assinada.
  2. Atualizador (Updater) — Adicionar os dados extras necessários para assinar as entradas.
  3. Assinante (Signer) — Assinar as entradas usando os dados extras e adicionar as assinaturas brutas à PSBT.
  4. Finalizador de Entradas (Input Finalizer) — Usar as assinaturas brutas para construir o código de destravamento completo das entradas.
  5. Extrator de Transação (Transaction Extractor) — Converter a PSBT nos dados brutos da transação assinada, pronta para ser transmitida à rede.

Há também um útil papel de Combinador (Combiner), que pode ser usado em qualquer passo intermediário:

Os passos acima não precisam ser executados em uma única sequência. Por exemplo, você poderia Atualizar/Assinar/Finalizar uma entrada de uma PSBT, e então enviá-la a outra pessoa, que pode voltar e Atualizar/Assinar/Finalizar outra entrada.

Na maioria das vezes, uma única carteira/entidade desempenha vários papéis de uma vez. Por exemplo, ao usar um dispositivo de hardware para assinar uma PSBT v0, a carteira watch-only será o Criador/Atualizador, e o dispositivo de hardware será o Assinante/Finalizador de Entradas/Extrator de Transação (o papel de Combinador não é necessário).

Mesmo assim, cada papel individual é especializado, e esses papéis permitem dividir o trabalho entre múltiplas carteiras/entidades para máxima flexibilidade ao processar PSBTs.

Abaixo está uma lista dos papéis para PSBTs da versão 0 e da versão 2. Há pequenas variações, mas o processo geral é, em linhas gerais, o mesmo.

Versão 0

Diagrama mostrando uma visão geral dos papéis/passos individuais envolvidos no processamento de uma PSBT v0.

1. Criador

Um Criador cria uma nova PSBT.

Ele constrói os dados iniciais da transação não assinada e os armazena na seguinte chave:

Esses dados da transação não assinada devem ser uma transação serializada com os campos de ScriptSig e Testemunha vazios.

2. Atualizador

Um Atualizador adiciona os dados extras necessários para criar as assinaturas dos dados da transação não assinada.

Isso quase sempre envolve incluir os dados necessários da(s) transação(ões) anterior(es) para cada entrada, por meio das seguintes chaves:

Opcionalmente, o Atualizador também pode especificar um tipo de SIGHASH para cada entrada:

Se uma entrada é um P2SH ou P2WSH, ela também deve incluir o script personalizado que foi usado (ou seja, o Redeem Script para um P2SH, ou o Witness Script para um P2WSH) para criar o Hash do Script colocado no ScriptPubKey da saída.

Esses scripts personalizados originais são necessários para conseguir destravar entradas P2SH e P2WSH. Eles devem ter sido armazenados pela carteira que criou o endereço para receber pagamentos.

Por fim, um Atualizador também pode adicionar informações úteis sobre as chaves públicas (e caminhos de derivação) usadas nas entradas e saídas.

As chaves acima ajudam as carteiras a identificar quais entradas elas podem assinar, bem como quais saídas são usadas para troco. Uma carteira pode identificar ambas as coisas sem essas chaves, mas incluí-las ajuda a carteira a identificá-las mais rápido.

Carteiras de hardware podem depender dessas chaves para identificar as entradas que podem assinar, já que podem não ter o poder de processamento para fazê-lo sem elas.

Em resumo, um Atualizador deve adicionar à PSBT quaisquer dados extras a que tenha acesso. O objetivo principal de um Atualizador é adicionar todos os dados relevantes aos mapas de entrada, para que um Assinante tenha tudo o que precisa para assinar as entradas.

Uma PSBT pode ser passada entre múltiplos Atualizadores para obter todos os dados extras necessários para a assinatura (ex.: se um único Atualizador não tem acesso a todas as informações exigidas).

3. Assinante

Um Assinante usa os dados já fornecidos na PSBT para criar assinaturas para as entradas.

A assinatura resultante é colocada na seguinte chave de cada mapa de entrada:

Se uma entrada exige múltiplas assinaturas (ex.: um script de travamento estilo P2MS), os mapas de entrada acabarão com múltiplas chaves PSBT_IN_PARTIAL_SIG.

O campo de dados da chave PSBT_IN_PARTIAL_SIG contém a chave pública correspondente à assinatura. Isso evita chaves duplicadas se houver múltiplas assinaturas em um único mapa de entrada.

Os Assinantes devem assinar quantas entradas da PSBT puderem, supondo que tenham as chaves privadas para assiná-las.

Uma PSBT pode ser passada entre múltiplos Assinantes se múltiplas assinaturas forem necessárias de pessoas diferentes, com cada um adicionando chaves PSBT_IN_PARTIAL_SIG às entradas conforme avançam.

Verificações

Um Assinante deve realizar verificações antes de assinar uma entrada.

Essas verificações dependem dos dados fornecidos no mapa de entrada:

4. Finalizador de Entradas

Um Finalizador de Entradas pega as assinaturas das chaves PSBT_IN_PARTIAL_SIG (adicionadas pelo(s) Assinante(s)) e as usa para construir o código de destravamento completo de cada entrada.

Os scripts de destravamento resultantes são colocados em uma das seguintes chaves:

A chave que você usa depende de se a entrada é destravada pelo campo ScriptSig (ex.: P2PK, P2PKH, P2SH) ou pelo campo de Testemunha (ex.: P2WPKH, P2WSH, P2TR).

Todas as outras chaves (exceto PSBT_IN_NON_WITNESS_UTXO e PSBT_IN_WITNESS_UTXO) devem ser apagadas do mapa de entrada após o código de destravamento ser adicionado. As chaves de UTXO são mantidas para que o Extrator de Transação possa verificar que a transação final está correta.

Se um Finalizador de Entradas ainda não tem dados suficientes para criar o código que destrava uma entrada (ex.: assinaturas insuficientes para destravar um multisig), o mapa de entrada não deve ser atualizado.

5. Extrator de Transação

Um Extrator de Transação converte a PSBT na transação final assinada.

Ele pega os dados das chaves PSBT_IN_FINAL_SCRIPTSIG e PSBT_IN_FINAL_SCRIPTWITNESS de cada entrada e os insere nos dados brutos da transação não assinada em PSBT_GLOBAL_UNSIGNED_TX.

O resultado é uma transação serializada pronta para ser transmitida à rede.

Todo mapa de entrada precisa ter um PSBT_IN_FINAL_SCRIPTSIG ou PSBT_IN_FINAL_SCRIPTWITNESS para conseguir construir a transação final assinada.

Combinador

Um Combinador mescla múltiplas PSBTs em uma única PSBT.

A PSBT resultante precisa conter todos os pares chave-valor de cada uma das PSBTs individuais. Quaisquer duplicatas precisam ser removidas.

Se houver pares chave-valor com a mesma chave mas valores diferentes, o Combinador pode escolher qual par chave-valor manter. Porém, o Combinador pode se recusar a combinar as PSBTs se for provável que haja inconsistências ou conflitos na PSBT resultante.

PSBTs só devem ser combinadas se forem para a mesma transação não assinada. O identificador único de uma PSBT v0 são os dados da transação não assinada em PSBT_GLOBAL_UNSIGNED_TX.

Versão 2

Uma PSBT v2 divide os dados da transação não assinada e os armazena em chaves separadas pelos três mapas.

Essa mudança de design facilita adicionar e remover entradas da PSBT antes da assinatura.

Para acomodar essa mudança, um novo papel foi adicionado (Construtor), e alguns dos papéis existentes foram levemente modificados. Mas não há uma diferença enorme no geral, e o processo geral para trabalhar com PSBTs permanece o mesmo.

Diagrama mostrando uma visão geral dos papéis/passos individuais envolvidos no processamento de uma PSBT v2.

1. Criador

Um Criador v2 cria uma nova PSBT.

Porém, na v2, o Criador ainda não inclui os dados da transação não assinada. Em vez disso, ele simplesmente adiciona as configurações básicas da PSBT no mapa global.

Primeiro, o número de versão da PSBT precisa ser definido como 2 para indicar que você está usando a PSBT v2:

Em seguida, defina o número de versão da transação e defina um valor opcional de fallback para o locktime da transação:

Por fim, o campo modificável deve ser definido para permitir que entradas e saídas sejam adicionadas à PSBT:

Porém, este último campo pode ser omitido se o Criador também for um Construtor.

2. Construtor

Um Construtor v2 adiciona as entradas e saídas a serem incluídas nos dados da transação não assinada.

Para cada nova entrada, as seguintes chaves precisam ser adicionadas ao mapa da entrada:

Essas chaves permitem referenciar as saídas que você quer usar como entradas da transação.

Além disso, cada entrada pode especificar um locktime (se necessário):

Você pode adicionar uma, ambas ou nenhuma.

Se você está adicionando uma entrada com apenas um desses tipos de chave de locktime, precisa verificar que ela não entra em conflito com os tipos de chave de locktime dos outros mapas de entrada. Por exemplo, se uma entrada já tem apenas PSBT_IN_REQUIRED_TIME_LOCKTIME, você não pode adicionar uma entrada com apenas PSBT_IN_REQUIRED_HEIGHT_LOCKTIME (e vice-versa).

Além disso, se a PSBT já foi assinada, você não pode adicionar uma entrada que especifique um locktime que mude o valor final de locktime da transação inteira. Veja a seção Cálculo do Locktime no papel Extrator de Transação para detalhes.

Para cada nova saída, as seguintes chaves precisam ser adicionadas ao mapa da saída:

Depois de adicionar uma nova entrada ou saída à PSBT, os campos de contagem no mapa global precisam ser incrementados:

Por fim, se nenhuma outra entrada ou saída deve ser adicionada à transação, isso deve ser declarado atualizando ou removendo a chave modificável no mapa global:

Se a chave PSBT_GLOBAL_TX_MODIFIABLE tem a flag Has SIGHASH_SINGLE (bit 2) definida como Verdadeiro, você precisa iterar pelas entradas e encontrar as que têm assinaturas que usam SIGHASH_SINGLE. O mesmo número de entradas e saídas precisa ser adicionado antes dessas entradas e das suas saídas correspondentes.

3. Atualizador

Um Atualizador v2 funciona da mesma forma que um Atualizador v0.

Porém, ele também pode definir o número de sequence de cada entrada:

Se esta chave não for definida, o número de sequence assume o padrão de 0xFFFFFFFF.

4. Assinante

Um Assinante v2 é o mesmo que um Assinante v0.

Porém, após assinar uma entrada, um Assinante v2 deve atualizar a chave modificável para refletir o estado da PSBT:

Este campo precisa ser atualizado com base no tipo de SIGHASH que o Assinante adicionou à entrada:

5. Finalizador de Entradas

Um Finalizador de Entradas v2 funciona da mesma forma que um Finalizador de Entradas v0.

Porém, após adicionar o PSBT_IN_FINAL_SCRIPTSIG ou PSBT_IN_FINAL_SCRIPTWITNESS relevante, as seguintes chaves v2 não devem ser removidas dos mapas de entrada (se presentes):

Essas chaves são necessárias para o Extrator de Transação construir a transação final assinada.

6. Extrator de Transação

Um Extrator de Transação v2 converte a PSBT em uma transação assinada bruta que pode ser transmitida à rede.

Porém, na v2 este passo é um pouco mais elaborado, pois você primeiro precisa construir os dados da transação não assinada a partir das chaves individuais adicionadas à PSBT pelo Criador v2 e pelo Construtor v2.

Você também precisará calcular o campo de locktime da transação olhando as chaves de locktime relevantes no mapa global e no(s) mapa(s) de entrada.

Por fim, os campos PSBT_IN_FINAL_SCRIPTSIG e PSBT_IN_FINAL_SCRIPTWITNESS (adicionados pelo Finalizador de Entradas) são mesclados aos dados da transação não assinada para formar a transação final assinada.

Na v0, os dados completos da transação não assinada já estão preparados no PSBT_GLOBAL_UNSIGNED_TX. Na v2, os dados da transação não assinada precisam ser construídos a partir dos dados armazenados nas chaves individuais pelo mapa global, mapa(s) de entrada e mapa(s) de saída.

Cálculo do Locktime

Para calcular o campo de locktime da transação, você precisa percorrer cada mapa de entrada para verificar os valores individuais de locktime que possam ter sido definidos para cada entrada.

O algoritmo básico é o seguinte:

  1. Se uma ou mais entradas têm um valor de locktime especificado, use o valor de locktime mais alto presente entre as entradas.*
  2. Se nenhuma entrada tem um valor de locktime especificado, use o valor de fallback no mapa global (PSBT_GLOBAL_FALLBACK_LOCKTIME).
  3. Se um locktime de fallback não estiver definido, use o valor de locktime padrão de 0 (ou seja, sem locktime).

*Para cada entrada, há na verdade dois tipos de chave de locktime diferentes que podem ser especificados:

  1. PSBT_IN_REQUIRED_TIME_LOCKTIME — Especifica o locktime desejado da transação em segundos (Unix Time).
  2. PSBT_IN_REQUIRED_HEIGHT_LOCKTIME — Especifica o locktime desejado da transação como uma altura de bloco.

Para determinar qual tipo de locktime usar, você precisa verificar quais tipos são suportados por todas as entradas:

Se ambos os tipos são suportados por todas as entradas (ou seja, você tem escolha entre os dois), você precisa usar o PSBT_IN_REQUIRED_HEIGHT_LOCKTIME de valor mais alto. Isso porque ele tem precedência sobre o PSBT_IN_REQUIRED_TIME_LOCKTIME (mesmo que este tenha um valor mais alto no geral).

Porém, se há um conflito em que um ou mais mapas de entrada suportam apenas um tipo de locktime, e um ou mais entradas suportam apenas o outro tipo de locktime, o campo de locktime da transação não pode ser calculado.

Idealmente, um Construtor não adicionará uma entrada com uma chave de locktime conflitante. Mas um Extrator de Transação ainda precisa verificar conflitos ao determinar o valor final do campo de locktime da transação.

Combinador

Um Combinador v2 funciona da mesma forma que um Combinador v0.

Formato

Qual formato de dados as PSBTs usam?

Há dois formatos "oficiais" para PSBTs:

  1. Binário (Arquivo) — Usado para passar uma PSBT adiante como um arquivo (usando a extensão .psbt).
  2. Base64 (Texto) — Usado para copiar/colar, ou armazenar uma PSBT como texto.

Então, se você exportar uma PSBT de uma carteira, deverá obter ou um arquivo binário ou uma string base64. Qualquer um serve, e ambos representam os mesmos dados subjacentes, então você pode converter diretamente entre os dois formatos:

# converte arquivo binário em string base64
base64 -w0 file.psbt
# nota: -w0 evita quebras de linha, que podem causar problemas na decodificação

# converte string base64 em arquivo binário
echo "base64psbtgoeshere" | base64 -d > file.psbt

Mas, às vezes, é mais fácil inspecionar PSBTs em formato hex, então pode ser útil converter uma PSBT de binário/base64 para formato hex, se necessário:

# converte arquivo binário em string hex (sem formatação)
xxd -p file.psbt | tr -d '\n' && echo ''

# converte string base64 em string hex (sem formatação)
echo "base64psbtgoeshere" | base64 -d | xxd -p | tr -d '\n' && echo ''

Formato Hex. Exibi os dados das PSBTs em formato hex ao longo desta página, pois geralmente é mais fácil de usar ao testar/depurar. Mas tecnicamente não é um formato no qual as PSBTs devam ser passadas adiante, e a maioria das carteiras não suporta exportar/importar uma PSBT em formato hex.

História

Quando as PSBTs foram introduzidas?

A ideia original de ter um formato padrão para exportar transações não assinadas foi proposta por Pieter Wuille (que também criou o nome "Partially Signed Bitcoin Transactions"). O formato PSBT em si foi então projetado e implementado por Ava Chow.

A primeira versão (PSBTv0) foi introduzida em 2017:

A segunda versão (PSBTv2) foi introduzida em 2021:

Essa segunda versão usa a mesma estrutura de dados de PSBT de antes, mas introduz novos tipos de chave e modifica alguns papéis para permitir que entradas e saídas sejam adicionadas aos dados da transação não assinada antes da assinatura. Então é basicamente uma versão um pouco mais flexível em comparação com a PSBTv0.

O Bitcoin Core ainda não oferece funcionalidade para suportar a PSBTv2, mas ela atualmente é suportada por outras carteiras de terceiros, como a Sparrow Wallet e a Coldcard.

Não existe PSBTv1. A primeira versão foi chamada de v0 e, como ela era comumente referida como a "primeira versão", a segunda versão foi chamada de v2 em vez de v1 para ajudar a evitar confusão (exceto, claro, pela confusão sobre por que não há uma "v1").

Resumo

Em poucas palavras

Uma PSBT é apenas uma transação que inclui todos os dados extras necessários para assiná-la.

A maioria das carteiras modernas entende o formato PSBT, então, se você está criando um software que pode exportar/importar transações não assinadas, vai querer pegar o jeito de lidar com PSBTs.

Elas podem parecer intimidadoras no início, por causa de todos os tipos de chave disponíveis, mas você não precisa entender todos eles para tirar proveito das PSBTs — você só precisa dos que são relevantes para as necessidades do seu software. O resto está lá apenas para o caso de você precisar.

Então, se você está começando com PSBTs, comece descobrindo como decodificá-las e codificá-las. A partir daí, você pode simplesmente usar as chaves de que precisa para assinar os tipos de entrada específicos que a sua carteira precisa lidar.

A maioria das carteiras só implementa a funcionalidade para lidar com um subconjunto de todos os tipos de chave possíveis. Por exemplo, para processar uma PSBT v0 para assinar um tipo de entrada simples como P2PKH, você só precisa trabalhar com os seguintes tipos de chave:

Então, como sempre, comece pela funcionalidade básica e siga a partir daí. Você pode descobrir como usar os outros tipos de chave conforme avança.

Divirta-se.

Recursos

Agradecimentos