Chaves Estendidas

Chaves que podem derivar novas chaves em uma carteira HD

BIP 32

Diagrama mostrando como chaves privadas estendidas e chaves públicas estendidas podem ser usadas para derivar chaves filhas.

Uma chave estendida (extended key) é uma chave privada ou chave pública que pode ser usada para derivar novas chaves em uma carteira HD.

Você pode ter uma única chave privada estendida e usá-la como fonte para todas as chaves privadas e chaves públicas filhas da sua carteira.

Além disso, uma chave privada estendida tem uma chave pública estendida correspondente, que pode gerar apenas as chaves públicas filhas.

Chaves Estendidas

Derive uma chave estendida filha a partir de uma chave estendida pai.

Chave Estendida Pai
Tipo
0 bytes 0 bytes

O número de índice desta filha com relação ao pai

0d
Tipo de índice
Chave Estendida Filha 0 bytes 0 bytes 0 bytes

Uma chave privada estendida é a chave privada mais o chain code.

Uma chave pública estendida é a chave pública mais o chain code.

Nunca use uma chave privada gerada por um site, nem insira sua chave privada em um site. Sites podem facilmente salvar a chave privada e usá-la para roubar seus bitcoins.

Conteúdo

Chave Estendida Mestra

Diagrama animado mostrando como a chave estendida mestra é criada a partir de uma seed.
A chave estendida mestra é criada passando a seed pelo HMAC com a string "Bitcoin seed".

A chave estendida mestra (master extended key) é a primeira chave estendida da carteira. Ela é criada passando a seed pela função HMAC-SHA512.

Ícone Ferramenta HMAC-SHA512

HMAC-SHA512

Se usa al derivar chaves estendidas.

semente ou (chave privada/pública + índice de 4 bytes)

0 bytes

"Bitcoin seed" ou chain code

0 bytes
HMAC-SHA512

HMAC-SHA512(dados, chave)

0 bytes

O HMAC retorna 64 bytes de dados (que são totalmente imprevisíveis). Dividimos em duas metades para criar a chave estendida mestra:

  • HMAC é um método de hash que permite passar dados junto com uma chave secreta adicional para produzir novos bytes aleatórios.
  • O motivo de passar a seed pelo HMAC-SHA512 (mesmo que a seed já tenha 64 bytes) é que a seed podia ter entre 128 e 512 bits (16 a 64 bytes) na especificação original do BIP 32 (2012). Porém, desde o BIP 39 (2013), todas as seeds geradas a partir de uma sentença mnemônica já têm 64 bytes.
  • O código de cadeia é necessário para gerar chaves filhas. Se alguém obtiver a chave privada mas não o código de cadeia, não conseguirá derivar as chaves descendentes (protegendo-as).

Aí estão nossa chave privada estendida mestra e chave pública estendida mestra.

Chaves estendidas não são nada especiais — são apenas um par de chaves normais que compartilham o mesmo código de cadeia (32 bytes extras de dados secretos). A mágica das chaves estendidas está em como geramos suas filhas.

Código

require 'openssl' # HMAC
require 'ecdsa'   # sudo gem install ecdsa

# ----
# Semente
# ----
seed = "67f93560761e20617de26e0cb84f7234aaf373ed2e66295c3d7397e6d7ebe882ea396d5d293808b0defd7edd2babd4c091ad942e6a9351e6d075a29d4df872af"
puts "seed: #{seed}"
puts

# --------------------
# Gerar Chaves Mestras
# --------------------
# semente
# |
# m

# HMAC
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, "Bitcoin seed", [seed].pack("H*")) # digest, chave, dados
master_private_key = hmac[0..63] # lado esquerdo do digest
master_chain_code = hmac[64..-1] # lado direito do digest

# > The SHA512-HMAC function is reused because it is already part of the standard elsewhere, but it takes a key in addition to the data being hashed.
# As the key can be arbitrary, we opted to use to make sure the key derivation was Bitcoin-specific. -- Pieter Wuille
# Tradução: A função SHA512-HMAC é reutilizada porque já faz parte do padrão em outros lugares, mas ela recebe uma chave além dos dados sendo hasheados. Como a chave pode ser arbitrária, optamos por usá-la para garantir que a derivação de chave fosse específica do Bitcoin.

# Obter Chave Pública (multiplica ponto gerador pela chave privada)
master_public_key = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(master_private_key.to_i(16)) # multiplica ponto gerador pela chave privada
master_public_key = ECDSA::Format::PointOctetString.encode(master_public_key, compression: true).unpack("H*")[0] # codifica para formato de chave pública comprimida

puts "master_chain_code:  #{master_chain_code}"  #=> 463223aac10fb13f291a1bc76bc26003d98da661cb76df61e750c139826dea8b
puts "master_private_key: #{master_private_key}" #=> f79bb0d317b310b261a55a8ab393b4c8a1aba6fa4d08aef379caba502d5d67f9
puts "master_public_key:  #{master_public_key}"  #=> 0252c616d91a2488c1fd1f0f172e98f7d1f6e51f8f389b2f8d632a8b490d5f6da9

Árvore de Chaves Estendidas

Diagrama de árvore mostrando como as chaves estendidas podem ser usadas para derivar diversas filhas.

Todas as chaves estendidas podem derivar chaves estendidas filhas.

Cada filha também tem um número de índice de até 4294967295 (o maior número em 4 bytes).

Por segurança, você pode derivar dois tipos de filhas a partir de uma chave privada estendida:

  1. Normal — Tanto a chave privada estendida quanto a chave pública estendida geram a mesma chave pública.
    Índices 0 a 2147483647 (primeira metade de todas as filhas possíveis)
  2. Endurecida (Hardened) — Apenas a chave privada estendida pode gerar a chave pública.
    Índices 2147483648 a 4294967295 (segunda metade de todas as filhas possíveis)

Em outras palavras, uma filha endurecida dá a opção de criar uma chave pública "secreta" ou "interna", já que ela não pode ser derivada a partir de uma chave pública estendida.

A derivação endurecida deve ser o padrão, a menos que haja uma boa razão para precisar gerar chaves públicas sem acesso à chave privada.
Pieter Wuille, bitcoin.stackexchange.com

O fato de a chave pública estendida poder gerar as mesmas chaves públicas que a chave privada estendida é o que as torna úteis.

Derivação de Chave Filha

Ambas as chaves privadas estendidas e chaves públicas estendidas podem derivar filhas, cada uma com seu próprio número de índice único.

Há 3 métodos para derivar chaves filhas:

  1. Chave Privada Estendida (Filha Normal)
  2. Chave Privada Estendida (Filha Endurecida)
  3. Chave Pública Estendida (Filha Normal)

Não é possível derivar uma chave pública estendida endurecida.

Chaves filhas derivadas são independentes entre si. Ou seja, você não saberia que duas chaves públicas em uma carteira (isto é, uma árvore de chaves estendidas) estão conectadas de forma alguma.

Chave Privada Estendida (Filha Normal)

Diagrama mostrando a derivação de uma chave privada estendida filha normal.
Scalar addition significa apenas adição aritmética tradicional.

Uma filha normal de chave privada estendida é criada a partir de uma chave privada estendida pai usando os seguintes passos:

  1. Calcule a chave pública da chave privada estendida pai.
    Isso é importante porque a chave pública estendida correspondente usará os mesmos dados na HMAC ao derivar suas filhas.
  2. Use um índice entre 0 e 2147483647.
    Índices nesta faixa são designados para filhas normais.
  3. Passe os dados e a chave pela HMAC:
    • data = chave pública de 33 bytes | índice de 4 bytes (concatenados)
    • key = código de cadeia de 32 bytes

O código de cadeia filho são os últimos 32 bytes do resultado da HMAC. É apenas um conjunto único de bytes que podemos usar como novo código de cadeia.

A chave privada filha são os primeiros 32 bytes do resultado da HMAC somados à chave privada do pai. Isso basicamente pega a chave privada original e a aumenta por um número aleatório de 32 bytes. Também aplicamos módulo na chave privada filha pela ordem da curva para mantê-la dentro do intervalo válido de números para a curva elíptica.

Em resumo, usamos os dados dentro da chave privada estendida pai (chave pública + índice, código de cadeia) e os passamos pela HMAC para produzir novos bytes aleatórios. Usamos esses novos bytes para construir a chave privada filha e o código de cadeia filho.

Podemos produzir chaves filhas completamente diferentes apenas mudando o índice. Alterar o número do índice muda a entrada da HMAC e, portanto, muda completamente o resultado.

Código

require 'openssl' # HMAC
require 'ecdsa'   # sudo gem install ecdsa

# ---------------------------------
# Chave Privada Estendida Filha Normal
# ---------------------------------
# m
# |- m/0
# |- m/1
# |- m/2
# ...

parent_chain_code  = "463223aac10fb13f291a1bc76bc26003d98da661cb76df61e750c139826dea8b"
parent_private_key = "f79bb0d317b310b261a55a8ab393b4c8a1aba6fa4d08aef379caba502d5d67f9"
parent_public_key  = "0252c616d91a2488c1fd1f0f172e98f7d1f6e51f8f389b2f8d632a8b490d5f6da9"
i = 0 # número do índice filho

# Prepara dados e chave para passar pela função HMAC
data = [parent_public_key].pack("H*") + [i].pack("N") # chave pública + índice
key = [parent_chain_code].pack("H*") # chain code é a chave para o hmac

hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, key, data) # digest, chave, dados
il = hmac[0..63]  # lado esquerdo da chave intermediária [32 bytes]
ir = hmac[64..-1] # lado direito da chave intermediária [32 bytes]

# Chain code são os últimos 32 bytes
child_chain_code = ir

# Verifica se o chain code é válido.
if child_chain_code.to_i(16) >= ECDSA::Group::Secp256k1.order
    raise "O chain code é maior que a ordem da curva. Tente o próximo índice."
end

# Calcula a chave privada filha
child_private_key = (il.to_i(16) + parent_private_key.to_i(16)) % ECDSA::Group::Secp256k1.order # (il + chave_pai) % n
child_private_key = child_private_key.to_s(16).rjust(64, '0') # converte para hex (e garante que tem 32 bytes)

# Calcula a chave pública correspondente também (opcional)
child_public_key = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(child_private_key.to_i(16)) # calcula a chave pública para esta também
child_public_key = ECDSA::Format::PointOctetString.encode(child_public_key, compression: true).unpack("H*")[0] # codifica para formato de chave pública comprimida

# Resultados
puts "child_chain_code:   #{child_chain_code}"  #=> 05aae71d7c080474efaab01fa79e96f4c6cfe243237780b0df4bc36106228e31
puts "child_private_key:  #{child_private_key}" #=> 39f329fedba2a68e2a804fcd9aeea4104ace9080212a52ce8b52c1fb89850c72
puts "child_public_key:   #{child_public_key}"  #=> 030204d3503024160e8303c0042930ea92a9d671de9aa139c1867353f6b6664e59

Chave Privada Estendida (Filha Endurecida)

Diagrama mostrando a derivação de uma chave privada estendida filha endurecida.
Scalar addition significa apenas adição aritmética tradicional.

Uma filha endurecida de chave privada estendida é criada a partir de uma chave privada estendida pai usando os seguintes passos:

  1. Use um índice entre 2147483648 e 4294967295.
    Índices nesta faixa são designados para filhas endurecidas.
  2. Passe os dados e a chave pela HMAC:
    • data = 0x00 | chave privada de 32 bytes | índice de 4 bytes (concatenados)
      Nota: não esqueça o prefixo 0x00. Ele distingue como chave privada e faz os dados terem o mesmo comprimento de quando uma chave pública é usada.
    • key = código de cadeia de 32 bytes

O código de cadeia filho são os últimos 32 bytes do resultado da HMAC.

A chave privada filha são os primeiros 32 bytes do resultado da HMAC somados à chave privada do pai. Novamente, isso apenas pega a chave privada original e a aumenta por um número aleatório de 32 bytes.

Observe que esta filha endurecida de chave privada estendida foi construída passando a chave privada do pai (em vez da chave pública do pai) para a HMAC — algo que uma chave pública estendida não tem acesso.

Como resultado, as filhas endurecidas de chaves privadas estendidas têm chaves públicas que não podem ser derivadas por uma chave pública estendida correspondente.

Código

require 'openssl' # HMAC
require 'ecdsa'   # sudo gem install ecdsa

# -----------------------------------
# Chave Privada Estendida Filha Endurecida
# -----------------------------------
# m
# ...
# |- m/2147483648
# |- m/2147483649
# |- m/2147483650
# ...

parent_chain_code  = "463223aac10fb13f291a1bc76bc26003d98da661cb76df61e750c139826dea8b"
parent_private_key = "f79bb0d317b310b261a55a8ab393b4c8a1aba6fa4d08aef379caba502d5d67f9"
i = 2147483648 # número do índice filho (deve estar entre 2**31 e 2**32-1)

# Prepara dados e chave para passar pela função HMAC
data = ["00"].pack("H*") + [parent_private_key].pack("H*") + [i].pack("N")  # 0x00 + chave_privada + índice
key = [parent_chain_code].pack("H*") # chain code é a chave para o hmac

hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, key, data) # digest, chave, dados
il = hmac[0..63]  # lado esquerdo da chave intermediária [32 bytes]
ir = hmac[64..-1] # lado direito da chave intermediária [32 bytes]

# Chain code são os últimos 32 bytes
child_chain_code = ir

# Verifica se o chain code é válido.
if child_chain_code.to_i(16) >= ECDSA::Group::Secp256k1.order
    raise "O chain code é maior que a ordem da curva. Tente o próximo índice."
end

# Calcula a chave privada filha
child_private_key = (il.to_i(16) + parent_private_key.to_i(16)) % ECDSA::Group::Secp256k1.order # (il + chave_pai) % n
child_private_key = child_private_key.to_s(16).rjust(64, '0') # converte para hex (e garante que tem 32 bytes)

# Calcula a chave pública correspondente também (opcional)
child_public_key = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(child_private_key.to_i(16)) # calcula a chave pública para esta também
child_public_key = ECDSA::Format::PointOctetString.encode(child_public_key, compression: true).unpack("H*")[0] # codifica para formato de chave pública comprimida

puts "child_chain_code:   #{child_chain_code}"  #=> cb3c17166cc30eb7fdd11993fb7307531372e565cd7c7136cbfa4655622bc2be
puts "child_private_key:  #{child_private_key}" #=> 7272904512add56fef94c7b4cfc62bedd0632afbad680f2eb404e95f2d84cbfa
puts "child_public_key:   #{child_public_key}"  #=> 0355cff4a963ce259b08be9a864564caca210eb4eb35fcb75712e4bba7550efd95

Chave Pública Estendida (Filha Normal)

Diagrama mostrando a derivação de uma chave pública estendida filha normal.
Point addition se refere a somar dois pontos na curva elíptica.

Uma filha normal de chave pública estendida é criada a partir de uma chave pública estendida pai usando os seguintes passos:

  1. Use um índice entre 0 e 2147483647.
    Índices nesta faixa são designados para filhas normais.
  2. Passe os dados e a chave pela HMAC:
    • data = chave pública de 33 bytes + índice de 4 bytes (concatenados)
    • key = código de cadeia de 32 bytes

O código de cadeia filho são os últimos 32 bytes do resultado da HMAC. Será o mesmo código de cadeia da filha normal de chave privada estendida acima, porque os mesmos dados de entrada foram usados na HMAC.

A chave pública filha é o ponto da chave pública pai somado a outro ponto na curva criado a partir dos primeiros 32 bytes do resultado da HMAC (você multiplica o ponto gerador por este valor para obter este ponto).

Ícone Ferramenta EC Multiply

Multiplicação de Ponto (EC Multiply)

Multiplique um ponto na curva elíptica secp256k1.

Ponto 1
x:
0d
y:
0d
0d
Ponto 1 × Multiplicador
x:
0d
y:
0d
Ícone Ferramenta EC Add

Soma de Pontos (EC Add)

Some dois pontos na curva elíptica secp256k1.

Ponto 1
x:
0d
y:
0d
Ponto 2
x:
0d
y:
0d
Ponto 1 + Ponto 2
x:
0d
y:
0d

Em resumo, passamos os mesmos dados e chave pela HMAC que usamos ao gerar a filha normal de chave privada estendida.

A chave pública filha é então calculada via adição de ponto na curva elíptica usando os mesmos primeiros 32 bytes do resultado da HMAC (o que significa que ela corresponderá à chave privada na filha de chave privada estendida).

Código

require 'openssl' # HMAC
require 'ecdsa' # sudo gem install ecdsa

# --------------------------------
# Chave Pública Estendida Filha Normal
# --------------------------------
# m -------- p
#            |- p/0
#            |- p/1
#            |- p/3

parent_chain_code = "463223aac10fb13f291a1bc76bc26003d98da661cb76df61e750c139826dea8b"
parent_public_key = "0252c616d91a2488c1fd1f0f172e98f7d1f6e51f8f389b2f8d632a8b490d5f6da9"
i = 0 # número do índice filho

if i >= 2**31
    raise "Não é possível criar chaves públicas filhas endurecidas a partir de chaves públicas pai."
end

# Prepara dados e chave para passar pela função HMAC
key = [parent_chain_code].pack("H*")
data = [parent_public_key].pack("H*") + [i].pack("N") # 32 bits sem sinal, ordem de bytes network (big-endian)

hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, key, data)
il = hmac[0..63]  # lado esquerdo da chave intermediária [32 bytes]
ir = hmac[64..-1] # lado direito da chave intermediária [32 bytes]

# Chain code são os últimos 32 bytes
child_chain_code = hmac[64..-1]

if il.to_i(16) >= ECDSA::Group::Secp256k1.order
    raise "O resultado do digest é maior que a ordem da curva. Tente o próximo índice."
end

# Calcula a chave pública filha
point_hmac   = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(il.to_i(16))                               # converte hmac il para um ponto
point_public = ECDSA::Format::PointOctetString.decode([parent_public_key].pack("H*"), ECDSA::Group::Secp256k1) # converte chave_pública_pai para um ponto
point = point_hmac.add_to_point(point_public)                                                                  # adição de pontos

if (point == ECDSA::Group::Secp256k1.infinity)
    raise "O ponto da chave pública filha está no ponto de infinito. Tente o próximo índice."
end

child_public_key = ECDSA::Format::PointOctetString.encode(point, compression: true).unpack("H*")[0] # codifica para chave pública comprimida

puts "child_chain_code:   #{child_chain_code}"
puts "child_public_key:   #{child_public_key}"

Chave Pública Estendida (Filha Endurecida)

Não é possível derivar uma filha endurecida a partir de uma chave pública estendida. A derivação endurecida requer a chave privada do pai, e a chave pública estendida só contém a chave pública.

Matemática

Como funcionam as chaves estendidas?

Diagrama mostrando como a chave pública estendida cria chaves públicas correspondentes para a chave privada estendida.

Em outras palavras, como é possível que uma chave pública derivada de uma chave pública estendida corresponda a uma chave privada derivada de uma chave privada estendida?

Básico

Para ambas as chaves estendidas filhas, estamos passando as mesmas entradas na HMAC, então obtemos os mesmos dados como resultado. Usando os primeiros 32 bytes destes dados (que é basicamente um número), então:

E devido à forma como a matemática da curva elíptica funciona, a chave privada filha corresponderá à chave pública filha.

Técnico

Primeiro, lembre-se que uma chave pública é apenas o ponto gerador em uma curva elíptica multiplicado por uma chave privada:

Diagrama mostrando uma chave pública como o ponto gerador multiplicado pela chave privada.

Agora, se você aumentar esta chave privada pai por um número (os primeiros 32 bytes do resultado HMAC), obtemos uma nova chave privada filha. Quando multiplicamos esta chave privada filha pelo ponto gerador, obtemos a chave pública filha:

Diagrama mostrando uma chave pública como o ponto gerador multiplicado por uma chave privada ajustada.

Da mesma forma, se você pegar o mesmo número, convertê-lo em um ponto na curva e somar à chave pública pai, você termina com a mesma chave pública filha:

Diagrama mostrando uma chave pública sendo ajustada ao adicionar outro ponto na curva.

Código

require 'ecdsa'   # ECDSA Math (sudo gem install ecdsa)

# Chave Privada Original e Chave Pública
private_key = 12345
public_key  = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(private_key)
number = rand(1000000) # usado para modificar a chave privada e a chave pública independentemente

# Chave Pública Filha 1: (Chave Privada + Número) * Gerador
private_and_number = private_key + number
child_public_key1  = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(private_and_number)

# Chave Pública Filha 2: Chave Pública + (Número * Gerador)
number_point       = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(number)
child_public_key2  = public_key.add_to_point(number_point)

# Resultados
puts ECDSA::Format::PointOctetString.encode(child_public_key1, compression: true).unpack("H*") #=> e.g. 022861a4809b2d8eb9269abea605f47db91deb9f5385bcc10b94aac6a0b81cba3d
puts ECDSA::Format::PointOctetString.encode(child_public_key2, compression: true).unpack("H*") #=> e.g. 022861a4809b2d8eb9269abea605f47db91deb9f5385bcc10b94aac6a0b81cba3d
puts child_public_key1 == child_public_key2 #=> true

Endereço

Diagrama mostrando como uma chave estendida é convertida em um endereço.

Uma chave estendida pode ser convertida em um endereço para facilitar o transporte.

O endereço de uma chave estendida contém a chave privada/chave pública e o código de cadeia, junto com alguns metadados adicionais.

Antes de converter para um endereço, uma chave estendida é serializada nos seguintes campos:

Estrutura dos bytes de uma chave estendida serializada
Campo Tamanho (bytes) Descrição
Version 4 Prefixo: 0488ade4 (xprv) ou 0488b21e (xpub)
Depth 1 Profundidade na árvore (0 = mestra)
Fingerprint 4 Primeiros 4 bytes do HASH160 da chave pública pai
Child Number 4 Índice da filha (0 para a mestra)
Chain Code 32 O segredo extra de 32 bytes, que impede outros de derivar chaves filhas sem ele.
Key 33 Chave privada (prefixo 00 + 32 bytes) ou chave pública (33 bytes comprimida)
Notas

Version Bytes:
0488ade4 = xprv
0488b21e = xpub
049d7878 = yprv (BIP 49)
049d7cb2 = ypub (BIP 49)
04b2430c = zprv (BIP 84)
04b24746 = zpub (BIP 84)

Chaves Estendidas Mestras:
Depth = 00
Fingerprint = 00000000
Child Number = 00000000

No campo Key, uma chave privada (32 bytes) recebe o prefixo 00 para ser estendida ao mesmo comprimento de uma chave pública (33 bytes).

Um checksum é adicionado a estes dados serializados (para ajudar a detectar erros), antes de finalmente converter tudo para Base58 e criar o formato legível da chave estendida.

Uma chave privada estendida tem esta aparência:

xprv9tuogRdb5YTgcL3P8Waj7REqDuQx4sXcodQaWTtEVFEp6yRKh1CjrWfXChnhgHeLDuXxo2auDZegMiVMGGxwxcrb2PmiGyCngLxvLeGsZRq

Uma chave pública estendida tem esta aparência:

xpub67uA5wAUuv1ypp7rEY7jUZBZmwFSULFUArLBJrHr3amnymkUEYWzQJz13zLacZv33sSuxKVmerpZeFExapBNt8HpAqtTtWqDQRAgyqSKUHu

Como você pode ver, elas são bem longas comparadas a endereços típicos, mas isso porque contêm informações extras úteis sobre a chave estendida.

Ícone Ferramenta Address Extended Key

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.

O fingerprint, depth e child number não são necessários para derivar chaves filhas — eles apenas ajudam a identificar o pai da chave atual e sua posição na árvore.

O campo de 4 bytes para o child number é o motivo pelo qual chaves estendidas são limitadas a derivar filhas com índices entre 0 e 4.294.967.295 (0xffffffff).

Código

# -----
# Utilitários - Necessários para criar a fingerprint e o checksum, e converter string hex para Base58
# -----
require 'digest'

def create_fingerprint(parent_public_key)
    hash160 = Digest::RMD160.digest(Digest::SHA256.digest([parent_public_key].pack("H*"))) # aplica hash160
    fingerprint = hash160[0...4].unpack("H*").join # pega os primeiros 4 bytes (e converte para hex)
    return fingerprint
end

def hash256(hex)
    binary = [hex].pack("H*")
    hash1 = Digest::SHA256.digest(binary)
    hash2 = Digest::SHA256.digest(hash1)
    result = hash2.unpack("H*")[0]
    return result
end

def checksum(hex)
    hash = hash256(hex) # Hasheia os dados através de SHA256 duas vezes
    return hash[0...8]  # Retorna os primeiros 4 bytes (8 caracteres)
end

def base58_encode(hex)
    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

    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

# --------------------------
# Serialização de Chave Estendida
# --------------------------
parent_public_key = "0252c616d91a2488c1fd1f0f172e98f7d1f6e51f8f389b2f8d632a8b490d5f6da9" # necessário para criar a fingerprint

chain_code  = "05aae71d7c080474efaab01fa79e96f4c6cfe243237780b0df4bc36106228e31" # m/0
private_key = "39f329fedba2a68e2a804fcd9aeea4104ace9080212a52ce8b52c1fb89850c72" # m/0

# version + depth + fingerprint + childnumber + chain_code + key + (checksum)
#
# version:     coloca xprv ou xpub no início
# depth:       quantas vezes este filho foi derivado da chave mestra (0 = chave mestra)
# fingerprint: criada a partir da chave pública pai (permite identificar xprv e xpub adjacentes)
# childnumber: o índice desta chave filha a partir da chave pai
# chain_code:  o chain code atual sendo usado para esta chave
# key:         a chave privada ou pública para a qual você quer criar uma chave estendida serializada (prefixe 0x00 para privada)

version     = "0488ade4" # privada = 0x0488ade4 (xprv), pública = 0x0488b21e (xpub)
depth       = "01"
fingerprint = create_fingerprint(parent_public_key) #=> "018c1259"
childnumber = "00000000"
chain_code  = chain_code
key         = "00" + private_key  # prefixa 00 a chaves privadas (para terem 33 bytes, o mesmo que chaves públicas)

serialized = version + depth + fingerprint + childnumber + chain_code + key
extended_private_key = base58_encode(serialized + checksum(serialized))

puts "extended_private_key: #{extended_private_key}" #=> xprv9tuogRdb5YTgcL3P8Waj7REqDuQx4sXcodQaWTtEVFEp6yRKh1CjrWfXChnhgHeLDuXxo2auDZegMiVMGGxwxcrb2PmiGyCngLxvLeGsZRq

Código

Aqui estão exemplos completos de código para criar, derivar e serializar chaves estendidas no Bitcoin.

Chaves Estendidas (Código Completo)

Ruby

require 'openssl' # HMAC
require 'ecdsa'   # sudo gem install ecdsa

# ----
# Semente
# ----
seed = "67f93560761e20617de26e0cb84f7234aaf373ed2e66295c3d7397e6d7ebe882ea396d5d293808b0defd7edd2babd4c091ad942e6a9351e6d075a29d4df872af"
puts "seed: #{seed}"
puts

# --------------------
# Gerar Chaves Mestras
# --------------------
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, "Bitcoin seed", [seed].pack("H*"))
master_private_key = hmac[0..63]
master_chain_code = hmac[64..-1]

master_public_key = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(master_private_key.to_i(16))
master_public_key = ECDSA::Format::PointOctetString.encode(master_public_key, compression: true).unpack("H*")[0]

puts "master_chain_code:  #{master_chain_code}"
puts "master_private_key: #{master_private_key}"
puts "master_public_key:  #{master_public_key}"
puts

# --------------------------
# Chave Privada Estendida Filha
# --------------------------
parent_private_key = master_private_key
parent_chain_code  = master_chain_code
i = 0

# Normal
if i < 2**31
    point = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(parent_private_key.to_i(16))
    point = ECDSA::Format::PointOctetString.encode(point, compression: true).unpack("H*")[0]
    data = [point].pack("H*") + [i].pack("N")
end

# Endurecida
if i >= 2**31
  data = ["00"].pack("H*") + [parent_private_key].pack("H*") + [i].pack("N")
end

key = [parent_chain_code].pack("H*")
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, key, data)
il = hmac[0..63]
ir = hmac[64..-1]

child_chain_code = ir

if child_chain_code.to_i(16) >= ECDSA::Group::Secp256k1.order
    raise "O chain code é maior que a ordem da curva. Tente o próximo índice."
end

child_private_key = (il.to_i(16) + parent_private_key.to_i(16)) % ECDSA::Group::Secp256k1.order
child_private_key = child_private_key.to_s(16).rjust(64, '0')

if child_private_key.to_i(16) == 0
    raise "A chave privada filha é zero. Tente o próximo índice."
end

child_public_key = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(child_private_key.to_i(16))
child_public_key = ECDSA::Format::PointOctetString.encode(child_public_key, compression: true).unpack("H*")[0]

puts "child_chain_code:   #{child_chain_code}"
puts "child_private_key:  #{child_private_key}"
puts "child_public_key:   #{child_public_key}"
puts

# -------------------------
# Chave Pública Estendida Filha
# -------------------------
parent_public_key = master_public_key
parent_chain_code = master_chain_code
i = 0

if i >= 2**31
    raise "Não é possível criar chaves públicas filhas endurecidas a partir de chaves públicas pai."
end

key = [parent_chain_code].pack("H*")
data = [parent_public_key].pack("H*") + [i].pack("N")

hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, key, data)
il = hmac[0..63]
ir = hmac[64..-1]

child_chain_code = hmac[64..-1]

if il.to_i(16) >= ECDSA::Group::Secp256k1.order
    raise "O resultado do digest é maior que a ordem da curva. Tente o próximo índice."
end

point_hmac   = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(il.to_i(16))
point_public = ECDSA::Format::PointOctetString.decode([parent_public_key].pack("H*"), ECDSA::Group::Secp256k1)
point = point_hmac.add_to_point(point_public)

if (point == ECDSA::Group::Secp256k1.infinity)
    raise "O ponto da chave pública filha está no ponto de infinito. Tente o próximo índice."
end

child_public_key = ECDSA::Format::PointOctetString.encode(point, compression: true).unpack("H*")[0]

puts "child_chain_code:   #{child_chain_code}"
puts "child_public_key:   #{child_public_key}"
puts

# --------------------------
# Serialização de Chave Estendida
# --------------------------
require 'digest'

def create_fingerprint(parent_public_key)
    hash160 = Digest::RMD160.digest(Digest::SHA256.digest([parent_public_key].pack("H*")))
    fingerprint = hash160[0...4].unpack("H*").join
    return fingerprint
end

def hash256(hex)
    binary = [hex].pack("H*")
    hash1 = Digest::SHA256.digest(binary)
    hash2 = Digest::SHA256.digest(hash1)
    result = hash2.unpack("H*")[0]
    return result
end

def checksum(hex)
    hash = hash256(hex)
    return hash[0...8]
end

def base58_encode(hex)
    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

    i = hex.to_i(16)
    buffer = String.new

    while i > 0
        remainder = i % base
        i = i / base
        buffer = chars[remainder] + buffer
    end

    leading_zero_bytes = (hex.match(/^([0]+)/) ? $1 : '').size / 2

    ("1"*leading_zero_bytes) + buffer
end

parent_public_key = master_public_key

chain_code  = child_chain_code
private_key = child_private_key

version     = "0488ade4"
depth       = "01"
fingerprint = create_fingerprint(parent_public_key)
childnumber = "00000000"
chain_code  = chain_code
key         = "00" + private_key

serialized = version + depth + fingerprint + childnumber + chain_code + key
extended_private_key = base58_encode(serialized + checksum(serialized))

puts "extended_private_key: #{extended_private_key}"

PHP

<?php
// Nota: Requer https://github.com/Bit-Wasp/secp256k1-php

// ------------------
// Seed para Chave Mestra
// ------------------

$seed = "67f93560761e20617de26e0cb84f7234aaf373ed2e66295c3d7397e6d7ebe882ea396d5d293808b0defd7edd2babd4c091ad942e6a9351e6d075a29d4df872af";
echo "seed: $seed".PHP_EOL;
echo PHP_EOL;

// Hasheie através de HMAC-SHA512 e divida em duas metades
//
//             seed
//              |
//            |----|
//            |HMAC|
//            |----|
//              |
// |    priv    | chain code |
//
$hmac = hash_hmac("sha512", hex2bin($seed), "Bitcoin seed"); // algo, data, key, raw_output (opcional)
$master_private_key = substr($hmac, 0, 64); // primeira metade é a chave privada
$master_chain_code = substr($hmac, 64);     // segunda metade é o chain code
//
// m (priv, chain_code)
//
echo "master_chain_code:  $master_chain_code".PHP_EOL;
echo "master_private_key: $master_private_key".PHP_EOL;

// Use matemática de curva elíptica para ir da chave privada para a chave pública
//
// priv -> pub
//
$context = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); // define contexto da curva
$point = null; secp256k1_ec_pubkey_create($context, $point, hex2bin($master_private_key)); // obtém o ponto da chave pública
$serialized = ''; secp256k1_ec_pubkey_serialize($context, $serialized, $point, SECP256K1_EC_COMPRESSED); // serializa para chave pública comprimida
$master_public_key = bin2hex($serialized);

//
// m (priv, pub, chain_code)
//
echo "master_public_key:  $master_public_key".PHP_EOL;
echo PHP_EOL;

// ---------------------------------------------------------
// Chave Privada Estendida Pai para Chave Privada Estendida Filha (m/0)
// ---------------------------------------------------------
// m (priv, pub, chain code)
// |
// m/0 (priv, pub, chain code)
$parent_private_key = $master_private_key; // obtém chave privada do pai
$parent_chain_code  = $master_chain_code;   // obtém chain code do pai
$i = 0;                                    // índice desta filha em relação ao pai

// Filha Normal (chave pública estendida pode derivar suas chaves públicas)
if ($i < 2**31) { // primeira metade dos índices possíveis são filhas normais
    // Obtém o ponto da chave pública para a chave privada pai
    $parent_public_key = $master_public_key; // você pode derivar isto da chave privada pai usando matemática de curva elíptica
    // Os 'data' para o próximo HMAC são a chave pública pai + índice
    //  data = | chave pública pai (33 bytes) | i (4 bytes) |
    $data = pack("H*", $parent_public_key).pack("N", $i);
}

// Filha Endurecida (chave pública estendida não pode derivar suas chaves públicas)
if ($i >= 2**31 ) { // segunda metade dos índices possíveis são filhas endurecidas
    //  data = | 00 | chave privada pai (32 bytes) | i (4 bytes) |
    $data = pack("H*", "00").pack("H*", $master_private_key).pack("N", $i);
}

// Coloque data (chave pública ou privada) e chain code através de HMAC-SHA512
$hmac = hash_hmac("sha512", $data, hex2bin($parent_chain_code)); // algo, data, key
$left = substr($hmac, 0, 64);
$right = substr($hmac, 64);

// Chain code filho é o lado direito do HMAC-SHA512
// m
// |
// m/0 (chain code)
$child_chain_code = $right;
echo "child_chain_code:   $child_chain_code".PHP_EOL;

// Verifica se o chain code filho é válido (não maior que o número de pontos na curva)
if (hexdec($child_chain_code) >= 115792089237316195423570985008687907852837564279074904382605163141518161494337) {
    throw new Exception("Chain code filho é maior ou igual à ordem da curva elíptica. Tente o próximo índice.");
}

// Calcula a chave privada filha (lado esquerdo do hmac + chave privada pai) % ordem
function bchexdec($hex) { // função utilitária para trabalhar com adição de grandes números hexadecimais
    if(strlen($hex) == 1) {
        return hexdec($hex);
    } else {
        $remain = substr($hex, 0, -1);
        $last = substr($hex, -1);
        return bcadd(bcmul(16, bchexdec($remain)), hexdec($last));
    }
}

function bcdechex($dec) { // função utilitária para converter grande inteiro de volta para hexadecimal
    $last = bcmod($dec, 16);
    $remain = bcdiv(bcsub($dec, $last), 16);

    if($remain == 0) {
        return dechex($last);
    } else {
        return bcdechex($remain).dechex($last);
    }
}

// chave privada filha = (left + chave privada pai) % ordem
// m
// |
// m/0 (priv)
$add = bcadd(bchexdec($left), bchexdec($parent_private_key)); // adiciona lado esquerdo do hmac à chave privada pai
$mod = bcmod($add, "115792089237316195423570985008687907852837564279074904382605163141518161494337"); // módulo pela ordem da curva
$child_private_key = str_pad(bcdechex($mod), 64, "0", STR_PAD_LEFT); // garante 64 caracteres (preenche com zeros)

// Verifica se a chave privada filha é válida (diferente de zero)
if (hexdec($child_private_key === 0)) {
    throw new Exception("Chave privada filha é zero (então é inválida). Tente o próximo índice.");
}
echo "child_private_key:  $child_private_key".PHP_EOL;

// calcula a chave pública filha correspondente a partir desta chave privada filha
// m
// |
// m/0 (pub)
$context = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); // define contexto da curva
$point = null; secp256k1_ec_pubkey_create($context, $point, hex2bin($child_private_key)); // obtém o ponto da chave pública
$serialized = ''; secp256k1_ec_pubkey_serialize($context, $serialized, $point, SECP256K1_EC_COMPRESSED); // serializa para chave pública comprimida
$child_public_key = bin2hex($serialized);
echo "child_public_key:   $child_public_key".PHP_EOL; // nota: pode precisar preencher hex
echo PHP_EOL;

// -------------------------------------------------------
// Chave Pública Estendida Pai para Chave Pública Estendida Filha (m/0)
// -------------------------------------------------------
// m -------- p (pub, chain code)
//            |
//            p/0 (pub, chain code)
$parent_public_key = $master_public_key; // obtém chave pública do pai
$parent_chain_code = $master_chain_code; // obtém chain code do pai
$i = 0;                                  // índice desta filha em relação ao pai

// Verifica se você não está tentando criar uma chave pública filha endurecida
if ($i >= 2**31) {
    throw new Exception("Não é possível criar uma chave pública filha estendida endurecida. Filhas endurecidas de chave privada estendida são privadas.");
}

// Coloca data (chave pública pai + i) e chain code através de HMAC-SHA512
$hmac = hash_hmac("sha512", pack("H*", $parent_public_key).pack("N", $i), hex2bin($parent_chain_code)); // mesmos dados de quando se cria chave privada estendida filha normal
$left = substr($hmac, 0, 64);
$right = substr($hmac, 64);

// Chain code filho é o lado direito do HMAC-SHA512
// m -------- p
//            |
//            |p/0 (chain code)
$child_chain_code = $right;
echo "child_chain_code:   $child_chain_code".PHP_EOL;

// Verifica se o chain code filho é válido (não maior ou igual ao número de pontos na curva)
if (hexdec($child_chain_code) >= 115792089237316195423570985008687907852837564279074904382605163141518161494337) {
    throw new Exception("Chain code filho é maior ou igual à ordem da curva elíptica. Tente o próximo índice.");
}

// Calcula a chave pública filha: ponto(chave pública pai) + hmac left
// m -------- p
//            |
//            |p/0 (pub)

// ponto (chave pública pai)
$context = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);                 // define contexto da curva
$point_public = null; secp256k1_ec_pubkey_parse($context, $point_public, hex2bin($parent_public_key));  // ponto(chave pública pai)

// adiciona ponto(hmac left) ao ponto(chave pública pai)
secp256k1_ec_pubkey_tweak_add($context, $point_public, hex2bin($left)); // adiciona hmac left ao ponto da chave pública pai
$add = ''; secp256k1_ec_pubkey_serialize($context, $add, $point_public, SECP256K1_EC_COMPRESSED); // serializa para chave comprimida
$child_public_key = unpack("H*", $add)[1];
echo "child_public_key:   $child_public_key".PHP_EOL;
echo PHP_EOL;

// ----------------------
// Serializar Chave Estendida
// ----------------------
$parent_public_key = $master_public_key; // necessário para criar fingerprint

$chain_code = $child_chain_code;
$private_key = $child_private_key;

// | version | depth  | parent fingerprint |  index  | chain code | key (private or public) |
// | 4 bytes | 1 byte |      4 bytes       | 4 bytes |  32 bytes  |        33 bytes         |  = 78 bytes

function create_fingerprint($parent_public_key) {
    $hash160 = hash("ripemd160", hash("sha256", hex2bin($parent_public_key), true)); // hash160 da chave pública pai
    return substr($hash160, 0, 8); // retorna primeiros 4 bytes (8 caracteres hex)
}

$version     = "0488ade4";               // 0488ade4 = privada (xprv), 0488b21e = pública (xpub)
$depth       = "01";                     // quantas vezes esta filha foi derivada da chave mestra (0 = chave mestra)
$fingerprint = create_fingerprint($parent_public_key); // fingerprint da chave pai
$index       = "00000000";               // índice desta chave filha em relação ao pai (string hexadecimal de 4 bytes)
$chain_code  = $chain_code;              // chain code para esta chave
$key         = "00".$private_key;        // chave privada ou pública para serializar (prefixo 0x00 para privada)

// Serializa os dados
$serialized = $version.$depth.$fingerprint.$index.$chain_code.$key;

// Cria um checksum (hasheia duas vezes com sha256 e pega os primeiros 4 bytes)
$checksum = substr(hash("sha256", hash("sha256", hex2bin($serialized), true)), 0, 8);

// Codifica em Base58 o serializado+checksum
function base58_encode($hex){
    $base58chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

    if (strlen($hex) == 0) {
        return '';
    }

    // Converte a string hex para um inteiro base10
    $num = gmp_strval(gmp_init($hex, 16), 58);

    // Verifica se o número não é apenas 0 - o que seria todo padding.
    if ($num != '0') {
        $num = strtr($num, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv', $base58chars);
    }
    else {
        $num = '';
    }

    // Preenche os 1's iniciais
    $pad = '';
    $n = 0;
    while (substr($hex, $n, 2) == '00') {
        $pad .= '1';
        $n += 2;
    }

    return $pad . $num;
}

$master_extended_private_key = base58_encode($serialized.$checksum);
echo "master_extended_private_key: $master_extended_private_key".PHP_EOL;

Recursos