Artigo original: How to generate your very own Bitcoin private key

Em criptomoedas, uma chave privada permite aos usuários acessar suas carteiras. A pessoa que possui a chave privada controla completamente as moedas naquela carteira. Por essa razão, você deve mantê-la em segredo. Se você realmente quiser gerar sua própria chave, faz sentido que ela seja gerada de uma maneira segura.

Aqui, vou introduzir o assunto das chaves privadas e mostrar como você pode gerar sua própria chave usando várias funções criptográficas. Vou fornecer a descrição de um algoritmo e seu código em Python.

Eu preciso gerar uma chave privada?

Na maioria das vezes, não. Por exemplo, se você usar uma carteira web como a Coinbase ou Blockchain.info, elas criam e administram a chave privada para você. A mesma coisa acontece com as transações.

Carteiras de celulares e desktops também geram normalmente uma chave privada para você, embora elas também possam dar a opção de gerar sua própria chave.

Então, porque gerá-la mesmo assim? Aqui, eu cito alguns motivos meus:

  • Você quer ter certeza de que ninguém sabe qual é a chave
  • Você quer apenas aprender mais sobre criptografia e geração de números aleatórios (do inglês, random number generation, ou RNG)

O que exatamente é uma chave privada?

Formalmente, uma chave privada para Bitcoin (e muitas outras criptomoedas) é uma série de 32 bytes. Hoje em dia, existem muitas maneiras de gravar esses bytes. Eles podem ser gravados como uma sequência de caracteres (em inglês, string) de 256 zeros e uns (32 * 8 = 256) ou 100 jogadas de dados. Pode ser uma string binária, uma string de base 64, uma chave WIF (texto em inglês), uma frase mnemônica (texto em inglês) ou ainda uma string hexadecimal. Neste artigo, usaremos uma string hexadecimal de 64 caracteres.

lyrhBKkIKdFsCCrBdSXnaRYJrwj67NUaMXNy
A mesma chave privada, escrita em formatos diferentes.

Por que exatamente 32 bytes? Ótima pergunta! Para criar uma chave pública a partir de uma privada, Bitcoin usa o ECDSA, ou Algoritmo de Assinatura Digital de Curva Elíptica (em inglês, Elliptic Curve Digital Signature Algorithm). Mais especificamente, usa uma curva particular chamada secp256k1.

Essa curva tem um arranjo de 256 bits, tem uma entrada de 256 bits e uma saída contendo números de 256 bits. 256 bits são exatamente 32 bytes. Isso quer dizer que precisamos de 32 bytes de dados para alimentar esse algoritmo de curva.

Existe um requisito adicional para uma chave privada. Por usarmos ECDSA, a chave deve ser positiva e inferior ao arranjo da curva. O arranjo de secp256k1 é FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141, o que é relativamente grande: quase todo número de 32 bytes será menor que ele.

Modo iniciante

Então, como geramos um número de 32 bytes? A primeira coisa que nos vem à mente é usar uma biblioteca RNG na linguagem de sua escolha. Python até oferece uma maneira bonita de gerar esses números com apenas alguns bits necessários:

import random
bits = random.getrandbits(256)
# 30848827712021293731208415302456569301499384654877289245795786476741155372082
bits_hex = hex(bits)
# 0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32
private_key = bits_hex[2:]
# 4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32

Parece bom, mas na verdade não é. Bibliotecas RNG comuns não foram criadas para criptografia. Sendo assim, elas não são muito seguras. Elas geram números baseados em uma semente (em inglês seed), que por padrão, é a hora atual. Sendo assim, se você souber aproximadamente quando eu gerei os bits acima, tudo o que você precisa é tentar um ataque de força bruta com algumas variantes.

Quando você gera uma chave privada, deseja estar extremamente seguro. Lembre-se de que, se alguém souber a chave privada, poderá facilmente roubar todas as moedas da carteira correspondente. Você não terá chance nenhuma de recuperá-las de volta.

Então, vamos tentar uma maneira mais segura.

RNG criptograficamente forte

Junto com o método básico de RNG, linguagens de programação normalmente fornecem um RNG desenhado especificamente para operações criptográficas. Esse método é usualmente muito mais seguro, pois extrai entropia diretamente do sistema operacional. O resultado desse RNG é muito mais difícil de reproduzir. Você não pode obtê-lo apenas sabendo a hora que foi gerado ou sua seed, pois não há nenhuma seed. Bom, ao menos o usuário não insere nenhuma seed. Ela é criada pelo programa.

Em Python, um RNG criptograficamente forte é implementado pelo módulo secrets. Vamos modificar o código acima para tornar a geração da chave privada mais segura!

import secrets
bits = secrets.randbits(256)
# 46518555179467323509970270980993648640987722172281263586388328188640792550961
bits_hex = hex(bits)
# 0x66d891b5ed7f51e5044be6a7ebe4e2eae32b960f5aa0883f7cc0ce4fd6921e31
private_key = bits_hex[2:]
# 66d891b5ed7f51e5044be6a7ebe4e2eae32b960f5aa0883f7cc0ce4fd6921e31

Isso é incrível. Eu aposto que você não seria capaz de reproduzi-lo, mesmo com acesso ao meu PC. Podemos, no entanto, ir mais longe.

Sites especializados

Existem sites que geram esses números aleatórios para você. Vamos analisar apenas dois aqui. Um é o random.org, um gerador de números bem conhecido. Outro é o bitaddress.org, que é desenhado especificamente para geração de chaves privadas de Bitcoin.

O random.org poderianos ajudar a gerar uma chave? Com certeza, já que eles têm um serviço para geração de bytes aleatórios. Porém, dois problemas emergem aqui. O random.org afirma ser um gerador verdadeiramente aleatório, mas será que você pode confiar? Você consegue ter certeza que é, de fato, aleatório? Como ter certeza de que o proprietário não grava todos os resultados gerados, especialmente os que se parecem com chaves privadas? Essa resposta fica a seu critério. Ah, você também não pode executá-lo localmente, o que gera um problema adicional. Esse método, definitivamente, não é 100% seguro.

Já com o bitaddress.org, a história é completamente diferente. Ele é de código aberto (em inglês open source). Então, você pode checar o que há debaixo dos panos. É client-side, ou seja você pode fazer o download e executá-lo localmente, até mesmo sem conexão a internet.

Então, como funciona? Ele usa você – sim, você – como uma fonte de entropia. Ele pede que você mova seu mouse ou pressione teclas aleatórias. Você realiza esses passos até que seja inviável reproduzir os resultados.

y5eDywvm3A2NMywdEk2u6pQYUORSP42gtWr2
O processo de gerar uma entropia ao mover o mouse aleatoriamente. Os inúmeros símbolos mostram o agrupamento.

Tem interesse em descobrir como o bitaddress.org funciona? Para fins educacionais, vamos dar uma olhada no seu código e tentar reproduzi-lo em Python.

Nota rápida: o bitaddress.org fornece a chave privada em formato de WIF comprimido, que é parecido ao formato WIF (texto em inglês) que discutimos previamente. Para nossos propósitos, faremos o algoritmo retornar uma string hexadecimal. Dessa maneira, poderemos usá-la mais tarde na geração de uma chave pública.

Bitaddress: as especificidades

O Bitaddress cria uma entropia em duas maneiras: pelo movimento do mouse e pelo pressionamento das teclas. Vamos falar sobre ambos, mas vamos focar no pressionamento das teclas, já que é difícil de implementar o rastreamento do mouse na biblioteca do Python. Esperaremos o usuário apertar teclas suficientes até que tenhamos entropia suficiente. Então, geraremos uma chave.

O Bitaddress faz três coisas. Ele inicializa um array de bytes, tentando obter o máximo de entropia possível de seu computador, preenche o array com inserções do usuário (em inglês, inputs) e, então, gera uma chave privada.

O Bitaddress usa o array de 256 bytes para guardar a entropia. Esse array é reescrito em ciclos. Então, quando o array é preenchido pela primeira vez, o ponteiro vai para zero e o processo de preenchimento começa de novo.

O programa inicia um array com 256 bytes a partir de window.crypto. Então, ele escreve um número representativo de um momento específico (nesse caso, a hora atual – em inglês, timestamp) para obter 4 bytes adicionais de entropia. Por fim, ele obtém dados como o tamanho da tela, seu fuso horário, informações sobre os plugins do seu navegador, sua localização e mais. Isso resulta em outros 6 bytes.

Depois da inicialização, o programa continuamente espera os inputs do usuário para reescrever os bytes iniciais. Quando o usuário move o cursor, o programa escreve a posição do cursor. Quando o usuário pressiona teclas, o programa escreve o código da tecla pressionada.

Finalmente, o bitaddress usa a entropia acumulada para gerar uma chave privada. É preciso gerar 32 bytes. Para essa tarefa, o bitaddress usa um algoritmo RNG chamado ARC4. O programa inicializa o ARC4 com a hora atual e a entropia coletada e, então, coleta os bytes um por um 32 vezes.

Isso tudo é uma simplificação de como o programa funciona, mas eu espero que tenha dado uma ideia de como ele é. Você pode checar o algoritmo completo e seus detalhes no Github.

Faça você mesmo

Para nossos propósitos, construiremos uma versão mais simples do bitaddress. Não vamos coletar dados sobre a localização e a máquina do usuário, somente inseriremos a entropia via texto, visto que é bem desafiador receber continuamente a posição do mouse em um script do Python (dê uma olhada em PyAutoGUI se tiver interesse em fazer isso).

Isso nos traz para especificação formal de nossa biblioteca geradora. Primeiro, vamos inicializar um array de byte com RNG criptográfico. Depois, preencheremos com a timestamp e, finalmente, preencheremos com os caracteres inseridos pelo usuário. Depois que o agrupamento de seeds for preenchido, a biblioteca deixará o desenvolvedor criar a chave. Na verdade, o desenvolvedor será capaz de criar quantas chaves privadas ele quiser – tudo seguro de acordo com a entropia coletada.

Inicializando o agrupamento

Aqui, colocamos alguns bytes de RNG criptográfico e uma timestamp. __seed_int e__seed_byte são dois métodos auxiliares que inserem entropia ao nosso array agrupador. Note que usamos a biblioteca secrets.

def __init_pool(self):
    for i in range(self.POOL_SIZE):
        random_byte = secrets.randbits(8)
        self.__seed_byte(random_byte)
    time_int = int(time.time())
    self.__seed_int(time_int)
def __seed_int(self, n):
    self.__seed_byte(n)
    self.__seed_byte(n >> 8)
    self.__seed_byte(n >> 16)
    self.__seed_byte(n >> 24)
def __seed_byte(self, n):
    self.pool[self.pool_pointer] ^= n & 255
    self.pool_pointer += 1
    if self.pool_pointer >= self.POOL_SIZE:
        self.pool_pointer = 0

Alimentando com input

Aqui, primeiro colocamos uma timestamp e, então, inserimos os caracteres, um a um.

def seed_input(self, str_input):
    time_int = int(time.time())
    self.__seed_int(time_int)
    for char in str_input:
        char_code = ord(char)
        self.__seed_byte(char_code)

Gerando a chave privada

Essa parte pode parecer difícil, mas, na verdade, é bem simples.

Primeiro, precisamos gerar um número de 32 bytes usando nosso agrupador. Infelizmente, não podemos apenas criar nosso próprio objeto random e usá-lo somente para a geração da chave. Ao invés disso, existe um objeto compartilhado que é usado por qualquer código que está sendo executado em um único script.

O que isso significa para nós? Significa que a cada momento, em qualquer lugar do código, um simples random.seed(0) pode destruir toda nossa entropia coletada. Não queremos isso. Por sorte, o Python nos dá os métodos getstate e setstate. Dessa maneira, para salvar nossa entropia cada vez que geramos uma chave, nós lembramos o estado onde paramos e o configuramos na próxima vez que necessitamos fazer uma chave.

Em segundo lugar, queremos ter certeza de que nossa chave está em uma variação específica (1, CURVE_ORDER). Isso é um requisito para todas chaves privadas ECDSA. O CURVE_ORDER é o arranjo da curva secp256k1, que é FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141.

Por fim, por conveniência, convertemos para hexadecimal e cortamos a parte '0x'.

def generate_key(self):
    big_int = self.__generate_big_int()
    big_int = big_int % (self.CURVE_ORDER — 1) # key < curve order
    big_int = big_int + 1 # key > 0
    key = hex(big_int)[2:]
    return key
def __generate_big_int(self):
    if self.prng_state is None:
    seed = int.from_bytes(self.pool, byteorder=’big’, signed=False)
    random.seed(seed)
    self.prng_state = random.getstate()
    random.setstate(self.prng_state)
    big_int = random.getrandbits(self.KEY_BYTES * 8)
    self.prng_state = random.getstate()
    return big_int

Em ação

Vamos tentar usar a biblioteca. Na verdade, é bem simples: você pode gerar uma chave privada em três linhas de código!

kg = KeyGenerator()
kg.seed_input(‘Uma frase completamente aleatória. Eu joguei um dado e obtive 4.’)
kg.generate_key()
# 60cf347dbc59d31c1358c8e5cf5e45b822ab85b79cb32a9f3d98184779a9efc2

Veja você mesmo. A chave é aleatória e totalmente válida. Além disso, cada vez que você rodar o código, obterá um resultado diferente.

Conclusão

Como pudemos ver, existem muitas maneiras de se gerar uma chave privada. Elas diferem em simplicidade e segurança.

Gerar uma chave privada é apenas o primeiro passo. O próximo passo é extrair uma chave pública e uma carteira que será usada para receber seus pagamentos. O processo de gerar uma carteira difere um pouco para Bitcoin e Ethereum. Eu planejo escrever outros dois artigos explorando mais esse tópico.

Se você quiser brincar com o código, ele está nesse repositório do Github.

O autor também trabalhou em um curso de criptomoedas. A primeira parte (texto em inglês) é uma descrição detalhada de blockchain.

Ele também publica pensamentos aleatórios sobre criptografia e criptomoedas no Twitter. Talvez você queira conferir.