Compact Size
O campo de tamanho variável usado nas mensagens da rede
Um campo compact size é usado em mensagens da rede para indicar o tamanho de um campo a seguir ou o número de campos a seguir.
Ele pode armazenar números entre 0 e 18446744073709551615.
O tamanho do campo aumenta conforme o número que ele contém aumenta. Ou seja, números menores ocupam menos espaço. Isso significa que você não precisa usar um campo de tamanho fixo maior o tempo todo para acomodar o maior número aceitável.
Estrutura
Um campo compact size é uma estrutura de bytes de tamanho variável. O byte inicial indica o tamanho do campo e também indica os bytes que contêm o número.
| Byte Inicial | Número | Intervalo | Tamanho do Campo | Exemplo |
|---|---|---|---|---|
FC (e abaixo) | O byte atual | 0 - 252 | 1 byte | 64 (100) |
FD | Próximos 2 bytes | 253 - 65535 | 3 bytes | FDE803 (1.000) |
FE | Próximos 4 bytes | 65536 - 4294967295 | 5 bytes | FEA0860100 (100.000) |
FF | Próximos 8 bytes | 4294967296 - 18446744073709551615 | 9 bytes | FF00E40B5402000000 (10.000.000.000) |
Nota: Os bytes que contêm o número estão em little-endian.
Então, para números pequenos de 252 ou menos, você usa um único byte. Mas para números maiores você usa um prefixo de FD, FE ou FF, e o inteiro fica contido nos próximos 2, 4 ou 8 bytes.
O valor máximo que um campo compact size pode conter é 18446744073709551615, que é FFFFFFFFFFFFFFFFFF (um prefixo FF seguido de FFFFFFFFFFFFFFFFFF).
Na maioria das vezes você verá campos compact size contendo números de 252 ou menos. Então, à primeira vista, você pode supor que está olhando um campo simples de 1 byte e não perceber que está olhando um tipo especial de campo que pode variar de tamanho.
Um campo compact size começando com FF (para números de 8 bytes) é totalmente exagerado e nunca é usado no Bitcoin. Ele indicaria mais de 4 GB de dados a seguir, muito mais do que jamais caberia dentro de um bloco real.
Você pode, na verdade, usar o prefixo FF e então usar os próximos 8 bytes 0000000000000001 para indicar um valor de 1. Seria um desperdício de espaço, mas ainda seria tão válido quanto usar o único byte 01.
Exemplos
Aqui estão alguns exemplos dos diferentes prefixos compact size encontrados em dados de transação:
FC (e abaixo)
Um único byte de FC ou abaixo é de longe o mais comum:
- a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d – Esta é a famosa transação da pizza. Para formar a saída de 10.000 BTC, esta transação reuniu 131 entradas, então a contagem de entradas foi um campo compact size de 1 byte:
83.
Este é só um exemplo rápido. Você pode procurar qualquer transação na blockchain e encontrará campos compact size simples de 1 byte. Eles estão em todo lugar.
FD
Você vai esbarrar no prefixo FD de vez em quando. Isso acontece quando há uma contagem de entradas/saídas acima da média, ou se um scriptsig/scriptpubkey for excepcionalmente grande:
- 6bb9c31f15c6940d4bd664054e398e420425339aadc65e8c491cf1151fe7ff4b – Esta transação tem 965 entradas, então o campo compact size é
FDC503(não esqueça que os dois últimos bytes estão em little-endian, então03C5= 965). - e411dbebd2f7d64dafeef9b14b5c59ec60c36779d43f850e5e347abee1e1a455 – Esta transação tem um scriptpubkey excepcionalmente grande (tem
OP_CHECKSIGrepetido várias vezes por algum motivo). O script tem 4.026 bytes de comprimento, então o campo compact size éFDBA0F. - 3454605a6e24181a6061574720e93a79689865e7952c56c330ebcb98fa95e936 – Esta transação tem 254 saídas. Embora um único byte possa conter o número 254 em circunstâncias normais, em um campo compact size o valor máximo de 1 byte é 252. Então, neste caso, o prefixo
FDfoi usado e o número 254 foi codificado nos 2 bytes seguintes, resultando no campo compact sizeFDFE00.
FE e FF
Você vai esbarrar muito raramente nos prefixos FE ou FF (para números maiores que 65.535). Isso porque o tamanho máximo de scriptpubkey/scriptsig é 10.000 bytes (veja script.h). Além disso, devido ao limite de tamanho do bloco de 4.000.000 unidades de peso, seria impossível ter mais de 65.535 entradas em uma única transação, e seria incrivelmente difícil ter mais de 65.535 saídas.
A única vez que você encontra um prefixo FE ou FF na prática é quando ele foi usado incorretamente para armazenar um número que poderia ter sido colocado em um campo compact size menor.
Onde aparece
Os campos compact size são usados em vários lugares nos dados do Bitcoin. Aqui estão os mais comuns:
Dados de Transação
Campos compact size são usados em todo o conteúdo das transações brutas. Eles são usados para indicar:
- O número de entradas.
- O número de saídas.
- O tamanho do scriptsig.
- O tamanho do scriptpubkey.
- O número de elementos da testemunha. (Transações Segwit)
- O tamanho de cada elemento da testemunha.
Aqui está um exemplo de transação legada (414719d592b73341b77497165d9f46f6eff6c243469265f95d920b779c7a0492). Dividi os dados brutos da transação em campos individuais e destaquei os campos compact size em verde:
01000000
01 <- contagem de entradas
79fe743502ff8cd181121572fececac3feee5ef3034edfb3ccd2bfaa24537dae00000000
6b <- tamanho do scriptsig
483045022100d39e64d275f0e69d5a2722ad93e3e206e98bf03584525cec05b5fcb75dc3e5a8022071fc39e3784be3a76d8469ed13ade270d8da25677fc5a226c5e7223a85701c7c012102b0453d54d1e0c0b41a63b3ca898afc4cc4243ed0241a9cc116e37854969a2270
ffffffff
01 <- contagem de saídas
72c9000000000000
19 <- tamanho do scriptpubkey
76a91400bafac9185e183c1203025fbdac30a4be5af91088ac
00000000
Aqui está um exemplo de transação segwit (672d9428242a097e57c5def8b300d05068e0d85a1028ac3e93c9a487561f36c9), novamente com os campos compact size destacados em verde:
01000000
0001
01 <- contagem de entradas
53baeaeed4799240f2a48e99fcc6e504672120764d622e4e5af9fd04b37a8293
05000000
00 <- tamanho do scriptsig
ffffffff
01 <- contagem de saídas
4ac7010000000000
17 <- tamanho do scriptpubkey
a914f314b4ac619e1d3f96a5ffac796b17e0a47b52b987
02 <- contagem de elementos da testemunha
47 <- tamanho do elemento da testemunha
3044022064576f10eee1b679648965b72081a636ac46b21be3e36558585775fc523dbcdf0220440b31af77adcbc75cf79679406d8ba1e2c14ff03d02606725d29ffdaa028a5f01
21 <- tamanho do elemento da testemunha
021ce981c19e4f998b62091ffd960549ead5f8ced3de7fc919d5d4a25e6edf42cd
00000000
Dados de Bloco
Um campo compact size é usado uma vez dentro de um bloco bruto. Ele indica:
- O número de transações no bloco.
Por exemplo, este é o bloco gênesis (000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f), e ele tem apenas uma transação:
01000000
0000000000000000000000000000000000000000000000000000000000000000
3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a
29ab5f49
ffff001d
1dac2b7c
01 <- contagem de transações
01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000
Ele fica entre o cabeçalho do bloco e as transações. Esta é a única vez que o campo compact size aparece dentro de um bloco bruto (sem contar o interior das transações).
Mensagens da Rede
O campo compact size é usado nas várias mensagens que os nós enviam uns aos outros na rede bitcoin.
Por exemplo, o payload de uma mensagem "inv" usa um campo compact size para indicar o número de itens a seguir:
01 01000000aa325e9122aa39ca18c75aabe2a3ceaf9802acd1a40720925bfd77fff58ed821
Esta mensagem indica que há um item na lista, e é este TXID: 21d88ef5ff77fd5b922007a4d1ac0298afcea3e2ab5ac718ca39aa22915e32aa (ordem de bytes inversa).
Transações e blocos também são mensagens que trafegam pela rede bitcoin. Então o campo compact size ajuda a economizar espaço nas mensagens serializadas que são enviadas entre os nós. Você sempre quer enviar a menor quantidade de dados possível pela rede (por eficiência), e é por isso que o compact size é útil.
Benefícios
Por que campos compact size são usados no Bitcoin?
O campo compact size economiza alguns bytes extras de espaço.
Por exemplo, você consegue colocar confortavelmente alguns milhares de saídas em uma transação, mas na maioria das vezes você só cria uma ou duas. Para o campo contagem de saídas, uma solução básica seria torná-lo um campo fixo de 2 bytes o tempo todo para permitir um número grande de saídas em raras ocasiões, mesmo que na vasta maioria das vezes isso não seja necessário. Então, por exemplo, em 10 transações você teria os seguintes campos:
Campo Fixo de 2 Bytes:
Número | Bytes
--------|------
2 | 0002
2 | 0002
1 | 0001
2 | 0002
27 | 001B
3 | 0003
3000 | 0BB8
2 | 0002
1 | 0001
2 | 0002
TOTAL = 40 bytes (em 10 transações)
Mas, usando um campo compact size flexível, podemos usar 1 byte na maioria das vezes e expandir até 3 bytes (1 byte de prefixo + 2 bytes de número) para acomodar números maiores nas raras ocasiões em que precisamos. Nas mesmas 10 transações, por exemplo:
Campo Compact Size:
Número | Bytes
--------|------
2 | 02
2 | 02
1 | 01
2 | 02
27 | 1B
3 | 03
3000 | FD0BB8
2 | 02
1 | 01
2 | 02
TOTAL = 24 bytes (em 10 transações)
É uma técnica de economia de espaço pequena. Mas, quando você tem vários desses campos em uma transação, e centenas de milhares de transações viajando entre computadores todos os dias (e bilhões de transações armazenadas na blockchain), os bytes se somam.
Código
Aqui está um exemplo rápido de código mostrando como converter entre um inteiro e compact size em Ruby:
# pack() - converte um inteiro para bytes brutos de um comprimento e ordem de byte específicos (ex.: little-endian) de acordo com a diretiva dada
# unpack("H*") - converte bytes brutos em uma string hexadecimal
# Diretivas:
#
# C = inteiro de 8 bits
# S< = inteiro de 16 bits, little-endian
# L< = inteiro de 32 bits, little-endian
# Q< = inteiro de 64 bits, little-endian
def encode(i)
# converte inteiro para string hex com o prefixo correto dependendo do tamanho do inteiro
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
def decode(compactsize)
# pega o primeiro byte
first = compactsize[0...2]
# pega a quantidade certa de bytes da string hex, então converte esta string hex para inteiro
if (first == "fd") then i = [compactsize[2...6]].pack("H*").unpack("S<")[0]
elsif (first == "fe") then i = [compactsize[2...10]].pack("H*").unpack("L<")[0]
elsif (first == "ff") then i = [compactsize[2...18]].pack("H*").unpack("Q<")[0]
else i = [compactsize[0...2]].pack("H*").unpack("C")[0]
end
return i
end
# Exemplos de Codificação
puts encode(0) #=> 00
puts encode(252) #=> fc
puts encode(253) #=> fdfd00
puts encode(65535) #=> fdffff
puts encode(65536) #=> fe00000100
puts encode(4294967295) #=> feffffffff
puts encode(4294967296) #=> ff0000000001000000
puts encode(18446744073709551615) #=> ffffffffffffffffff
# Exemplos de Decodificação
puts decode("00") #=> 0
puts decode("fc") #=> 252
puts decode("fdfd00") #=> 253
puts decode("fdffff") #=> 65535
puts decode("fe00000100") #=> 65536
puts decode("feffffffff") #=> 4294967295
puts decode("ff0000000001000000") #=> 4294967296
puts decode("ffffffffffffffffff") #=> 18446744073709551615 Resumo
Um campo compact size é usado para indicar um número de itens a seguir ou o tamanho de alguns dados a seguir, em mensagens da rede (ex.: transações brutas). Normalmente tem 1 byte, mas pode se expandir até 9 bytes quando necessário para codificar números maiores.
Ele faz parte do protocolo desde o primeiro lançamento do Bitcoin (v0.1.0) e pode ser encontrado em serialize.h. Acredito que essa codificação compact size é algo que o Satoshi criou ao programar o Bitcoin, pois não a vi sendo usada em nenhum outro lugar.