Chaves Estendidas
Chaves que podem derivar novas chaves em uma carteira HD
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.
Chave Estendida Mestra
A chave estendida mestra (master extended key) é a primeira chave estendida da carteira. Ela é criada passando a seed pela função HMAC-SHA512.
HMAC-SHA512
O HMAC retorna 64 bytes de dados (que são totalmente imprevisíveis). Dividimos em duas metades para criar a chave estendida mestra:
- A primeira metade será a chave privada, como qualquer outra chave privada.
- A segunda metade será o código de cadeia (chain code), que são 32 bytes extras de dados secretos.
- 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
Todas as chaves estendidas podem derivar chaves estendidas filhas.
- Chaves privadas estendidas podem gerar filhas com novas chaves privadas e chaves públicas.
- Chaves públicas estendidas podem gerar filhas com novas chaves públicas apenas.
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:
- 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) - 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.
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:
- Chave Privada Estendida (Filha Normal)
- Chave Privada Estendida (Filha Endurecida)
- 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)
Uma filha normal de chave privada estendida é criada a partir de uma chave privada estendida pai usando os seguintes passos:
- 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. - Use um índice entre 0 e 2147483647.
Índices nesta faixa são designados para filhas normais. - 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)
Uma filha endurecida de chave privada estendida é criada a partir de uma chave privada estendida pai usando os seguintes passos:
- Use um índice entre 2147483648 e 4294967295.
Índices nesta faixa são designados para filhas endurecidas. - 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
- data = 0x00 | chave privada de 32 bytes | índice de 4 bytes (concatenados)
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)
Uma filha normal de chave pública estendida é criada a partir de uma chave pública estendida pai usando os seguintes passos:
- Use um índice entre 0 e 2147483647.
Índices nesta faixa são designados para filhas normais. - 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).
EC Multiply
EC Add
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?
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:
- Aumentamos a chave privada pai por este número para criar a chave privada filha.
- Aumentamos a chave pública pai pela mesma quantia para criar a chave pública filha.
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:
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:
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:
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
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:
| 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) |
NotasVersion Bytes: Chaves Estendidas Mestras: No campo Key, uma chave privada (32 bytes) recebe o prefixo | ||
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.
Address Extended Key
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
- BIP 32 — Especificação original para chaves estendidas por Pieter Wuille.
- Ian Coleman BIP39 Tool — Ferramenta online fantástica para derivar chaves estendidas de uma seed.
- Bitcoin Ruby: ext_key.rb — Implementação de chaves estendidas em Ruby.
- What makes an extended public or private key?
- Why hardened xpub key cannot generate child public key?
- HMAC "Bitcoin Seed" for BIP32
- What is the difference between MAC and HMAC?