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 é 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.
| 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.
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:
- Ele te dá um conjunto maior de caracteres para trabalhar. Isso significa que você pode representar números maiores usando menos caracteres.
- Ele deixa de fora caracteres incômodos. Isso ajuda a evitar erros ao transcrever.
Codificar
Converter 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
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:
- Endereços
- Uma chave pública é a contraparte "pública" de uma chave privada, e você as usa quando quer enviar bitcoins para alguém. No entanto, chaves públicas são bem longas, então as convertemos para endereços (o que faz uso do base58 no passo final da conversão).
- Chaves privadas WIF
- Uma chave privada é como uma "senha" que você usa para destravar bitcoins para gastar, e às vezes você pode querer importá-la para uma carteira. Para essa ocasião existe a chamada chave privada WIF, que é basicamente uma chave privada convertida para base58.
- Chaves estendidas
- Se você estiver usando uma Carteira HD, você pode se deparar com chaves privadas estendidas e chaves públicas estendidas. Essas também são convertidas para base58 para encurtá-las e torná-las mais fáceis de compartilhar.
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
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
| 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
| 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
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:
Endereço (Base58)
Chave privada WIF
Endereço (Chave Estendida)
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.
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
- src/base58.cpp
- github.com - base58.rb
- bitcoin.it/wiki/Base58Check_encoding
- darklaunch.com - Base58 Encoder/Decoder
Leitura Adicional
Agradecimentos
- TheSeven - Conversamos no IRC. Me ajudou a entender como o Base58 é implementado no Bitcoin.