Base58

Um conjunto de caracteres fácil de compartilhar

Conjunto de caracteres Base58

0 1 2 3 4 5 6 7 8 9
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
a b c d e f g h i j k l m n o p q r s t u v w x y z

Base58

Converta entre hexadecimal e a codificação Base58.

0 bytes

ou uma chave privada WIF, por exemplo>Uma

0 dígitos
 

Base58 é um conjunto de caracteres amigável que você pode usar para representar números grandes em um formato mais curto.

O Satoshi criou esse conjunto de caracteres na primeira versão do Bitcoin. Ele é usado para codificar endereços legados, chaves privadas WIF e chaves estendidas.

Terminologia

O que "base58" significa?

A "base" se refere ao número de caracteres que você usa para representar um valor.

Bases numéricas e seus conjuntos de caracteres
Base Caracteres
2 (binário) 01
10 (decimal) 0123456789
16 (hexadecimal) 0123456789abcdef
58 123456789ABCDEFGH JKLMN PQRSTUVWXYZabcdefghijk mnopqrstuvwxyz

No dia a dia, estamos acostumados a trabalhar com números base10 (usando os dez dígitos 0123456789).

Mas, se você é um computador, é fácil o bastante usar caracteres extras para representar valores:

base10(9999) = 9999
base16(9999) = 270f
base58(9999) = 3yQ

Todos esses "números" têm o mesmo valor – eles apenas usam conjuntos de caracteres diferentes (ou seja, bases) para representá-lo.

Quanto mais caracteres você tem na sua base, menos caracteres você precisa para representar números grandes.

Benefícios

Por que usamos Base58 no Bitcoin?

// Why base-58 instead of standard base-64 encoding?
// - Don't want 0OIl characters that look the same in some fonts and
//      could be used to create visually identical looking account numbers.
// - A string with non-alphanumeric characters is not as easily accepted as an account number.
// - E-mail usually won't line-break if there's no punctuation to break at.
// - Doubleclicking selects the whole number as one word if it's all alphanumeric.
Satoshi Nakamoto, Bitcoin v0.1 (base58.h)

Por que Base58? Por que esses 58 caracteres específicos?

Porque esses são os caracteres que sobram quando você usa todos os caracteres do alfabeto alfanumérico (62 no total), mas remove os caracteres facilmente confundíveis 0, O, l e I.

alfanumérico = 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
base58       =  123456789ABCDEFGH JKLMN PQRSTUVWXYZabcdefghijk mnopqrstuvwxyz

Então o base58 tem dois benefícios principais:

  1. Ele te dá um conjunto maior de caracteres para trabalhar. Isso significa que você pode representar números maiores usando menos caracteres.
  2. Ele deixa de fora caracteres incômodos. Isso ajuda a evitar erros ao transcrever.
Boneco palito olhando para um círculo tentando descobrir se é um 0 ou um O.
Um O/0 chato

Codificar

Converter um inteiro para Base58

Animação mostrando como codificar um inteiro para base58.

Para converter um inteiro (base10) para base58, você usa a função módulo1.

Basicamente, você continua dividindo o seu número por 58, pegando o resto a cada passo do caminho para obter o índice do próximo caractere base58, e para quando não restar mais nenhum resto.

Por exemplo:

base10 = 123456789

123456789 % 58 = 19 <- resto
  2128565 % 58 = 23 <- resto
    36699 % 58 = 43 <- resto
      632 % 58 = 52 <- resto
       10 % 58 = 10 <- resto

base58 = [10][52][43][23][19]
base58 = BukQL

Código

# Uma função simples que converte um _inteiro_ para base58:

def int_to_base58(i)

  characters = %w[
  1 2 3 4 5 6 7 8 9
  A B C D E F G H J K L M N P Q R S T U V W X Y Z
  a b c d e f g h i j k m n o p q r s t u v w x y z
  ]

  # cria uma string vazia (preparando para receber os novos caracteres)
  buffer = ''

  # continua achando o resto até nosso número inicial chegar a zero
  while i > 0
    # acha o resto após dividir por 58 (% = módulo)
    remainder = i % 58

    # adiciona o caractere base58 ao início da string
    buffer = characters[remainder] + buffer

    # divide nosso inteiro por 58 e repete...
    i = i / 58
  end

  return buffer
end

puts int_to_base58(123456789) #=> BukQL

Decodificar

Converter Base58 para um inteiro

Animação mostrando como decodificar base58 para um inteiro.

Para converter um valor base58 em base10 (inteiro), trabalhando da direita para a esquerda, você pega o índice de cada caractere Base58 e o multiplica por potências crescentes de 58, depois soma todos os valores.

Por exemplo:

base58 = BukQL

L = 19 * 58^0 = 19
Q = 23 * 58^1 = 1334
k = 43 * 58^2 = 144652
u = 52 * 58^3 = 10145824
B = 10 * 58^4 = 113164960

base10 = 19 + 1334 + 144652 + 10145824 + 113164960
base10 = 123456789

Código

def base58_to_int(base58)

  characters = %w[
      1 2 3 4 5 6 7 8 9
    A B C D E F G H   J K L M N   P Q R S T U V W X Y Z
    a b c d e f g h i j k   m n o p q r s t u v w x y z
    ]

  # cria um inteiro para guardar o resultado
  total = 0

  # inverte a string base58 para lermos os caracteres da direita para a esquerda
  base58 = base58.reverse

  # percorre cada caractere, incluindo o índice, para sabermos quantos caracteres já lemos
  base58.each_char.with_index do |char, i|

    # pega o número de índice deste caractere
    char_i = characters.index(char)

    # calcula quantos 58s este caractere representa (incrementa a potência a cada caractere)
    value  = char_i * (58**i)

    # adiciona ao total
    total = total + value
  end

  return total
end

puts base58_to_int("BukQL") #=> 123456789

Esse processo matemático é o mesmo ao converter de uma base para outra. Você pode ver um exemplo parecido ao converter de hexadecimal para decimal (base16 para base10).

Base58 no Bitcoin

Como o Base58 é usado no Bitcoin?

O Base58 é usado no Bitcoin quando você quer converter dados usados com frequência para um formato mais fácil de compartilhar. Por exemplo:

O Base58 só era usado para codificar endereços a princípio, mas passou a ser usado para codificar chaves privadas WIF e chaves estendidas quando elas foram introduzidas mais tarde.

Zeros à Esquerda

Diagrama mostrando bytes zero à esquerda em hexadecimal sendo convertidos para um 1 em base58.

No Bitcoin, convertemos cada byte zero (0x00) no início de um valor hexadecimal para um 1 em base58.

Colocar zeros no início de um número não aumenta o seu tamanho (ex.: 0x12 é igual a 0x0012), então, quando convertemos para base58, quaisquer zeros adicionais no início não afetam o resultado.

Portanto, para garantir que os zeros à esquerda tenham influência no resultado, a codificação base58 do Bitcoin inclui um passo manual para converter todos os 0x00's à esquerda em 1's.

0x: Um prefixo 0x indica um valor hexadecimal. Valores hexadecimais às vezes contêm apenas os números 0-9 e poderiam, portanto, ser confundidos com valores decimais, então o prefixo nos ajuda a distinguir entre eles. Esse prefixo é descartado antes de ser usado no cálculo.

Byte: Um byte de dados pode guardar um valor entre 0-255 e pode ser representado por dois caracteres hexadecimais. Por exemplo, 0xff é um byte de dados e representa o valor 255 em decimal.

Prefixos

No Bitcoin, diferentes prefixos são adicionados aos dados antes de converter para base58 para influenciar o caractere inicial do resultado, e esse caractere inicial nos ajuda a identificar o que cada string base58 representa.

Estes são os prefixos mais comuns usados no Bitcoin:

Mainnet

Prefixos da Mainnet
Prefixo Hex Caractere Inicial Base58 Representa Exemplo
00 1 Endereço (P2PKH) 1AKDDsfTh8uY4X3ppy1m7jw1fVMBSMkzjP
05 3 Endereço (P2SH) 34nSkinWC9rDDJiUY438qQN1JHmGqBHGW7
80 K, L ou 5 Chave privada WIF L4mee2GrpBSckB9SgC9WhHxvtEgKUvgvTiyYcGu38mr9CGKBGp93
0488ADE4 xprv Chave privada estendida xprv9tuogRdb5YTgcL3P8Waj7REqDuQx4sXcodQaWTtEVFEp6yRKh1CjrWfXChnhgHeLDuXxo2auDZegMiVMGGxwxcrb2PmiGyCngLxvLeGsZRq
0488B21E xpub Chave pública estendida xpub67uA5wAUuv1ypp7rEY7jUZBZmwFSULFUArLBJrHr3amnymkUEYWzQJz13zLacZv33sSuxKVmerpZeFExapBNt8HpAqtTtWqDQRAgyqSKUHu

Testnet

Prefixos da Testnet
Prefixo Hex Caractere Inicial Base58 Representa Exemplo
6F m ou n Endereço (P2PKH) ms2qxPw1Q2nTkm4eMHqe6mM7JAFqAwDhpB
C4 2 Endereço (P2SH) 2MwSNRexxm3uhAKF696xq3ztdiqgMj36rJo
EF c ou 9 Chave privada WIF cV8e6wGiFF8succi4bxe4cTzWTyj9NncXm81ihMYdtW9T1QXV5gS
04358394 tprv Chave privada estendida tprv9tuogRdb5YTgcL3P8Waj7REqDuQx4sXcodQaWTtEVFEp6yRKh1CjrWfXChnhgHeLDuXxo2auDZegMiVMGGxwxcrb2PmiGyCngLxvLeGsZRq
043587CF tpub Chave pública estendida tpub67uA5wAUuv1ypp7rEY7jUZBZmwFSULFUArLBJrHr3amnymkUEYWzQJz13zLacZv33sSuxKVmerpZeFExapBNt8HpAqtTtWqDQRAgyqSKUHu

https://en.bitcoin.it/wiki/List_of_address_prefixes

As chaves privadas WIF usam o mesmo prefixo hex, mas produzem caracteres iniciais diferentes. Isso acontece porque, em algumas situações, nós anexamos um byte 01 à chave privada antes de converter para base58, e esse byte extra afeta o caractere inicial.

As chaves estendidas contêm metadados extras junto com as chaves públicas e privadas originais, e é por isso que suas strings base58 são visivelmente mais longas.

Base58Check

Diagrama mostrando os passos da codificação Base58Check.

A codificação Base58Check é um nome resumido para adicionar um checksum a alguns dados antes de codificá-los em base58.

Aqui estão alguns exemplos comuns de dados do Bitcoin que usam a codificação Base58Check:

Ícone Ferramenta Endereço (Base58)

Endereço (Base58)

Codifique el hash160 de una clave pública o script en una dirección heredada.

1 byte
0 bytes
0 bytes
Tipo de dirección

Codificación Base58 de los datos anteriores

0 caracteres
Ícone Ferramenta Chave privada WIF

Chave Privada WIF

crua e o Wallet Import Format.>Converta entre uma

1 byte
Red
0 bytes
1 byte
Comprimida
0 bytes

Base58 dos dados anteriores

0 caracteres

    Nunca introduza sua chave privada em um site, nem use uma chave privada gerada por um site. Os sites podem guardar facilmente a chave privada e usá-la para roubar seus bitcoins.

    Ícone Ferramenta Endereço (Chave Estendida)

    Endereço (Chave Estendida)

    em um endereço.>Codifique uma

    Dados da chave estendida
    Tipo
    Legacy (BIP 44)

    P2PKH>Nota:

    Segwit (BIP 49)

    (P2SH-P2WPKH)>Nota:

    Segwit (BIP 84)

    P2WPKH>Nota:

    Quantas derivações de profundidade a partir da chave mestra (0 se for a mestra)

    0d

    da chave pública do pai (00000000 se for a mestra)>Os 4 primeiros bytes do

    O número de índice desta chave com relação ao pai (0 se for a mestra)

    0d

    da chave pai (key+index, chain code) ou (seed, passphrase)>Os últimos 32 bytes do

    0 bytes

    Chave privada crua (32 bytes) ou chave pública (33 bytes)

    0 bytes
    0 bytes
    0 bytes

    da chave estendida serializada e do checksum>Codificação

    0 caracteres

    Nunca use uma chave privada gerada por um site, nem introduza sua chave privada em um site. Os sites podem guardar facilmente a chave privada e usá-la para roubar seus bitcoins.

    Você às vezes vê esse termo "Base58Check" aparecer de vez em quando ao ler sobre codificação base58, então achei melhor cobri-lo aqui.

    Base58Check

    Cria um checksum para os dados e depois codifica em base58.

    0 bytes
    Esperado:
    0 caracteres

    Código

    Estes trechos de código realizam a conversão base58 completa usada no Bitcoin.

    Eles convertem de e para hexadecimal, porque esse é o formato mais comum de onde partimos ao converter para base58.

    Ruby

    module Base58
    
      @chars = %w[
          1 2 3 4 5 6 7 8 9
        A B C D E F G H   J K L M N   P Q R S T U V W X Y Z
        a b c d e f g h i j k   m n o p q r s t u v w x y z
    ]
      @base = @chars.length
    
      def self.encode(hex)
        i = hex.to_i(16)
        buffer = String.new
    
        while i > 0
          remainder = i % @base
          i = i / @base
          buffer = @chars[remainder] + buffer
        end
    
        # adiciona '1's ao início conforme o número de bytes zero à esquerda
        leading_zero_bytes = (hex.match(/^([0]+)/) ? $1 : '').size / 2
    
        ("1"*leading_zero_bytes) + buffer
      end
    
      def self.decode(base58)
        total = 0 # inteiro para guardar a conversão para decimal
    
        # percorre cada caractere
        base58.reverse.each_char.with_index do |char, i|
          char_i = @chars.index(char) # pega o número de índice deste caractere
          value  = (58**i) * char_i   # calcula quantos 58s este caractere representa
          total = total + value     # adiciona ao total
        end
    
        # converte este inteiro para hex
        hex = total.to_s(16)
    
        # adiciona 00s à esquerda para cada 1 à esquerda
        leading_1s = (base58.match(/^([1]+)/) ? $1 : '').size
    
        ("00"*leading_1s) + hex
      end
    
    end
    
    puts Base58.encode('0093ce48570b55c42c2af816aeaba06cfee1224faebb6127fe') #=> 1EUXSxuUVy2PC5enGXR1a3yxbEjNWMHuem
    puts Base58.decode('1EUXSxuUVy2PC5enGXR1a3yxbEjNWMHuem') #=> 0093ce48570b55c42c2af816aeaba06cfee1224faebb6127fe

    PHP

    <?php
    
    // Entrada de exemplo
    $hex = "00662ad25db00e7bb38bc04831ae48b4b446d1269817d515b6"; // um hash de chave pública (com prefixo 00)
    
    // -------------
    // Codificar Base58
    // -------------
    // Converte a string hex para um inteiro
    $num = gmp_init($hex, 16);
    $base58 = "";
    
    // Caracteres Base58
    $chars = str_split("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz");
    
    // Continua dividindo por 58 e pegando o resto como caractere
    while ($num > 0) {
        $rem = gmp_mod($num, 58); // resto (de onde tiramos o caractere)
        $num = gmp_div($num, 58); // quociente (continua dividindo o número para obter os restos)
        $base58 = $chars[intval($rem)].$base58; // adiciona o caractere base58 ao início
    }
    
    // Converte os 00s à esquerda em hex para 1s à esquerda (feito manualmente na conversão base58)
    $count = intval(strspn($hex, "0") / 2); // quantos 0s à esquerda, depois divide por 2 (para descobrir quantos bytes zero foram prefixados)
    $leading = str_repeat("1", $count); // prefixa um 1 para cada byte zero (ex.: 00)
    
    // Resultado
    $result = $leading.$base58;
    echo $result.PHP_EOL; // 1AKDDsfTh8uY4X3ppy1m7jw1fVMBSMkzjP
    
    // -------------
    // Decodificar Base58
    // -------------
    $base58 = "1AKDDsfTh8uY4X3ppy1m7jw1fVMBSMkzjP";
    $int = gmp_init(0); // inteiro para guardar o resultado
    
    // Converte para decimal
    $base58a = str_split(strrev($base58));   // cria um array para percorrermos
    foreach ($base58a as $i => $c) {         // percorre cada caractere
        $multiple = gmp_pow(58, $i);         // quantos 58s esta posição contém (ex.: 58^0, 58^1, 58^2...)
        $index = array_search($c, $chars);   // pega o número de índice do caractere base58 (ex.: B=10)
        $value = gmp_mul($index, $multiple); // multiplica para obter o número de 58s que este caractere representa
        $int = $int + $value;                // adiciona ao total
    }
    
    // Converte para hexadecimal
    $gmp = gmp_init(strval($int), 10); // cria um número gmp a partir da string (base 10) NOTA: gmp_init recebe strings
    $hex = gmp_strval($gmp, 16); // converte para representação em string hex
    if (strlen($hex) % 2 !== 0) { // retorna um número par de caracteres (hex2bin prefere assim)
        $hex = '0'.$hex;
    }
    
    // Converte os 1s à esquerda em base58 para 00s à esquerda (feito manualmente na conversão base58)
    $count = strspn($base58, "1");
    $leading = str_repeat("00", $count);
    
    // Resultado
    $result = $leading.$hex;
    echo $result.PHP_EOL; // 00662ad25db00e7bb38bc04831ae48b4b446d1269817d515b6

    Notas

    Módulo (%)

    O operador módulo (%) retorna o resto de uma divisão:

    7 % 6 = 1
    7 % 5 = 2
    7 % 4 = 3
    7 % 3 = 1
    

    É como uma irmã do operador de divisão (/).

    Recursos

    Leitura Adicional

    Agradecimentos