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

El número de índice de esta hija con respecto al padre

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.

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

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

# --------------------
# Generate Master Keys
# --------------------
# seed
# |
# m

# HMAC
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, "Bitcoin seed", [seed].pack("H*")) # digest, key, data
master_private_key = hmac[0..63] # left side of digest
master_chain_code = hmac[64..-1] # right side of 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

# Get Public Key (multiply generator point by private key)
master_public_key = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(master_private_key.to_i(16)) # multiply generator point by private key
master_public_key = ECDSA::Format::PointOctetString.encode(master_public_key, compression: true).unpack("H*")[0] # encode to compressed public key format

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

# ---------------------------------
# Normal Child Extended Private Key
# ---------------------------------
# m
# |- m/0
# |- m/1
# |- m/2
# ...

parent_chain_code  = "463223aac10fb13f291a1bc76bc26003d98da661cb76df61e750c139826dea8b"
parent_private_key = "f79bb0d317b310b261a55a8ab393b4c8a1aba6fa4d08aef379caba502d5d67f9"
parent_public_key  = "0252c616d91a2488c1fd1f0f172e98f7d1f6e51f8f389b2f8d632a8b490d5f6da9"
i = 0 # child index number

# Prepare data and key to put through HMAC function
data = [parent_public_key].pack("H*") + [i].pack("N") # public key + index
key = [parent_chain_code].pack("H*") # chain code is key for hmac

hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, key, data) # digest, key, data
il = hmac[0..63]  # left side of intermediate key [32 bytes]
ir = hmac[64..-1] # right side of intermediate key [32 bytes]

# Chain code is last 32 bytes
child_chain_code = ir

# Check the chain code is valid.
if child_chain_code.to_i(16) >= ECDSA::Group::Secp256k1.order
    raise "Chain code is greater than the order of the curve. Try the next index."
end

# Calculate child private key
child_private_key = (il.to_i(16) + parent_private_key.to_i(16)) % ECDSA::Group::Secp256k1.order # (il + parent_key) % n
child_private_key = child_private_key.to_s(16).rjust(64, '0') # convert to hex (and make sure it's 32 bytes long)

# Work out the corresponding public key too (optional)
child_public_key = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(child_private_key.to_i(16)) # work out the public key for this too
child_public_key = ECDSA::Format::PointOctetString.encode(child_public_key, compression: true).unpack("H*")[0] # encode to compressed public key format

# Results
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

# -----------------------------------
# Hardened Child Extended Private Key
# -----------------------------------
# m
# ...
# |- m/2147483648
# |- m/2147483649
# |- m/2147483650
# ...

parent_chain_code  = "463223aac10fb13f291a1bc76bc26003d98da661cb76df61e750c139826dea8b"
parent_private_key = "f79bb0d317b310b261a55a8ab393b4c8a1aba6fa4d08aef379caba502d5d67f9"
i = 2147483648 # child index number (must between 2**31 and 2**32-1)

# Prepare data and key to put through HMAC function
data = ["00"].pack("H*") + [parent_private_key].pack("H*") + [i].pack("N")  # 0x00 + private_key + index
key = [parent_chain_code].pack("H*") # chain code is key for hmac

hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, key, data) # digest, key, data
il = hmac[0..63]  # left side of intermediate key [32 bytes]
ir = hmac[64..-1] # right side of intermediate key [32 bytes]

# Chain code is last 32 bytes
child_chain_code = ir

# Check the chain code is valid.
if child_chain_code.to_i(16) >= ECDSA::Group::Secp256k1.order
    raise "Chain code is greater than the order of the curve. Try the next index."
end

# Calculate child private key
child_private_key = (il.to_i(16) + parent_private_key.to_i(16)) % ECDSA::Group::Secp256k1.order # (il + parent_key) % n
child_private_key = child_private_key.to_s(16).rjust(64, '0') # convert to hex (and make sure it's 32 bytes long)

# Work out the corresponding public key too (optional)
child_public_key = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(child_private_key.to_i(16)) # work out the public key for this too
child_public_key = ECDSA::Format::PointOctetString.encode(child_public_key, compression: true).unpack("H*")[0] # encode to compressed public key format

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

# --------------------------------
# Normal Child Extended Public Key
# --------------------------------
# m -------- p
#            |- p/0
#            |- p/1
#            |- p/3

parent_chain_code = "463223aac10fb13f291a1bc76bc26003d98da661cb76df61e750c139826dea8b"
parent_public_key = "0252c616d91a2488c1fd1f0f172e98f7d1f6e51f8f389b2f8d632a8b490d5f6da9"
i = 0 # child index number

if i >= 2**31
    raise "Can't create hardened child public keys from parent public keys."
end

# Prepare data and key to put through HMAC function
key = [parent_chain_code].pack("H*")
data = [parent_public_key].pack("H*") + [i].pack("N") # 32-bit unsigned, network (big-endian) byte order

hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, key, data)
il = hmac[0..63]  # left side of intermediate key [32 bytes]
ir = hmac[64..-1] # right side of intermediate key [32 bytes]

# Chain code is last 32 bytes
child_chain_code = hmac[64..-1]

if il.to_i(16) >= ECDSA::Group::Secp256k1.order
    raise "Result of digest is greater than the order of the curve. Try the next index."
end

# Work out the child public key
point_hmac   = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(il.to_i(16))                               # convert hmac il to a point
point_public = ECDSA::Format::PointOctetString.decode([parent_public_key].pack("H*"), ECDSA::Group::Secp256k1) # convert parent_public_key to a point
point = point_hmac.add_to_point(point_public)                                                                  # point addition

if (point == ECDSA::Group::Secp256k1.infinity)
    raise "Child public key point is at point of infinity. Try the next index."
end

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

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)

# Original Private Key and Public Key
private_key = 12345
public_key  = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(private_key)
number = rand(1000000) # used to modify the private key and public key independently

# Child Public Key 1: (Private Key + Number) * Generator
private_and_number = private_key + number
child_public_key1  = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(private_and_number)

# Child Public Key 2: Public Key + (Number * Generator)
number_point       = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(number)
child_public_key2  = public_key.add_to_point(number_point)

# Results
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

# -----
# Utils - Needed for creating the fingerprint and checksum, and converting hex string to Base58
# -----
require 'digest'

def create_fingerprint(parent_public_key)
    hash160 = Digest::RMD160.digest(Digest::SHA256.digest([parent_public_key].pack("H*"))) # hash160 it
    fingerprint = hash160[0...4].unpack("H*").join # take first 4 bytes (and convert to 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) # Hash the data through SHA256 twice
    return hash[0...8]  # Return the first 4 bytes (8 characters)
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

    # add '1's to the start based on number of leading bytes of zeros
    leading_zero_bytes = (hex.match(/^([0]+)/) ? $1 : '').size / 2

    ("1"*leading_zero_bytes) + buffer
end

# --------------------------
# Extended Key Serialization
# --------------------------
parent_public_key = "0252c616d91a2488c1fd1f0f172e98f7d1f6e51f8f389b2f8d632a8b490d5f6da9" # needed to create fingerprint

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

# version + depth + fingerprint + childnumber + chain_code + key + (checksum)
#
# version:     puts xprv or xpub at the start
# depth:       how many times this child has been derived from master key (0 = master key)
# fingerprint: created from parent public key (allows you to spot adjacent xprv and xpubs)
# childnumber: the index of this child key from the parent
# chain_code:  the current chain code being used for this key
# key:         the private or public key you want to create a serialized extended key for (prepend 0x00 for private)

version     = "0488ade4" # private = 0x0488ade4 (xprv), public = 0x0488b21e (xpub)
depth       = "01"
fingerprint = create_fingerprint(parent_public_key) #=> "018c1259"
childnumber = "00000000"
chain_code  = chain_code
key         = "00" + private_key  # prepend 00 to private keys (to make them 33 bytes, the same as public keys)

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

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

# --------------------
# Generate Master Keys
# --------------------
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

# --------------------------
# Child Extended Private Key
# --------------------------
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

# Hardened
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 "Chain code is greater than the order of the curve. Try the next index."
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 "Child private key is zero. Try the next index."
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

# -------------------------
# Child Extended Public Key
# -------------------------
parent_public_key = master_public_key
parent_chain_code = master_chain_code
i = 0

if i >= 2**31
    raise "Can't create hardened child public keys from parent public keys."
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 "Result of digest is greater than the order of the curve. Try the next index."
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 "Child public key point is at point of infinity. Try the next index."
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

# --------------------------
# Extended Key Serialization
# --------------------------
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
// Note: Requires https://github.com/Bit-Wasp/secp256k1-php

// ------------------
// Seed to Master Key
// ------------------

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

$hmac = hash_hmac("sha512", hex2bin($seed), "Bitcoin seed");
$master_private_key = substr($hmac, 0, 64);
$master_chain_code = substr($hmac, 64);

echo "master_chain_code:  $master_chain_code".PHP_EOL;
echo "master_private_key: $master_private_key".PHP_EOL;

$context = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
$point = null; secp256k1_ec_pubkey_create($context, $point, hex2bin($master_private_key));
$serialized = ''; secp256k1_ec_pubkey_serialize($context, $serialized, $point, SECP256K1_EC_COMPRESSED);
$master_public_key = bin2hex($serialized);

echo "master_public_key:  $master_public_key".PHP_EOL;
echo PHP_EOL;

// ---------------------------------------------------------
// Parent Extended Private Key to Child Extended Private Key
// ---------------------------------------------------------
$parent_private_key = $master_private_key;
$parent_chain_code  = $master_chain_code;
$i = 0;

// Normal Child
if ($i < 2**31) {
    $parent_public_key = $master_public_key;
    $data = pack("H*", $parent_public_key).pack("N", $i);
}

// Hardened Child
if ($i >= 2**31) {
    $data = pack("H*", "00").pack("H*", $master_private_key).pack("N", $i);
}

$hmac = hash_hmac("sha512", $data, hex2bin($parent_chain_code));
$left = substr($hmac, 0, 64);
$right = substr($hmac, 64);

$child_chain_code = $right;
echo "child_chain_code:   $child_chain_code".PHP_EOL;

if (hexdec($child_chain_code) >= 115792089237316195423570985008687907852837564279074904382605163141518161494337) {
    throw new Exception("Child chain code is greater than or equal to the order of the elliptic curve. Try next index.");
}

function bchexdec($hex) {
    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) {
    $last = bcmod($dec, 16);
    $remain = bcdiv(bcsub($dec, $last), 16);

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

$add = bcadd(bchexdec($left), bchexdec($parent_private_key));
$mod = bcmod($add, "115792089237316195423570985008687907852837564279074904382605163141518161494337");
$child_private_key = str_pad(bcdechex($mod), 64, "0", STR_PAD_LEFT);

echo "child_private_key:  $child_private_key".PHP_EOL;

$context = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
$point = null; secp256k1_ec_pubkey_create($context, $point, hex2bin($child_private_key));
$serialized = ''; secp256k1_ec_pubkey_serialize($context, $serialized, $point, SECP256K1_EC_COMPRESSED);
$child_public_key = bin2hex($serialized);
echo "child_public_key:   $child_public_key".PHP_EOL;
echo PHP_EOL;

// -------------------------------------------------------
// Parent Extended Public Key to Child Extended Public Key
// -------------------------------------------------------
$parent_public_key = $master_public_key;
$parent_chain_code = $master_chain_code;
$i = 0;

if ($i >= 2**31) {
    throw new Exception("Cannot create a hardened extended child public key. Hardened children of extended private key are private.");
}

$hmac = hash_hmac("sha512", pack("H*", $parent_public_key).pack("N", $i), hex2bin($parent_chain_code));
$left = substr($hmac, 0, 64);
$right = substr($hmac, 64);

$child_chain_code = $right;
echo "child_chain_code:   $child_chain_code".PHP_EOL;

if (hexdec($child_chain_code) >= 115792089237316195423570985008687907852837564279074904382605163141518161494337) {
    throw new Exception("Child chain code is greater than or equal to the order of the elliptic curve. Try next index.");
}

$context = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
$point_public = null; secp256k1_ec_pubkey_parse($context, $point_public, hex2bin($parent_public_key));

secp256k1_ec_pubkey_tweak_add($context, $point_public, hex2bin($left));
$add = ''; secp256k1_ec_pubkey_serialize($context, $add, $point_public, SECP256K1_EC_COMPRESSED);
$child_public_key = unpack("H*", $add)[1];
echo "child_public_key:   $child_public_key".PHP_EOL;
echo PHP_EOL;

// ----------------------
// Serialize Extended Key
// ----------------------
$parent_public_key = $master_public_key;

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

function create_fingerprint($parent_public_key) {
    $hash160 = hash("ripemd160", hash("sha256", hex2bin($parent_public_key), true));
    return substr($hash160, 0, 8);
}

$version     = "0488ade4";
$depth       = "01";
$fingerprint = create_fingerprint($parent_public_key);
$index       = "00000000";
$chain_code  = $chain_code;
$key         = "00".$private_key;

$serialized = $version.$depth.$fingerprint.$index.$chain_code.$key;

$checksum = substr(hash("sha256", hash("sha256", hex2bin($serialized), true)), 0, 8);

function base58_encode($hex){
    $base58chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

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

    $num = gmp_strval(gmp_init($hex, 16), 58);

    if ($num != '0') {
        $num = strtr($num, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv', $base58chars);
    }
    else {
        $num = '';
    }

    $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