Getting from seed words to the first SegWit address
obrusvit opened this issue · comments
Hi, this is a question. I'm currently trying to develop a small program which derives SegWit receive addresses from given mnemonic. The derivation path should be m/84'/0'/0'/0
. I tried with the current master
branch of libbitcoin-system
. This is how I proceeded:
- initialize testing seed words:
charge food clutch wrap crash fun age celery fringe electric energy congress
and no passphrase - create a
mnemonic
object from the seed words - create a
context
object forp2wpkh
- create a master private key from
mnemonic
andcontext
(if I get it right, we are now atm/84'/0'
, i.e. purpose and coin) - derive hardened private key from the master private key (to get to
m/84'/0'/0'
, i.e. primary account) - derive normal private key from the key from previous point (to get to
m/84'/0'/0'/0
, i.e. not change) - derive public key from the key from previous point (to get to
m/84'/0'/0'/0/0
, i.e. first public key of primary address) - creating a
witness_address
object from the key from previous point, I expect the output to bebc1qen0rl0sej2s5s9rhfmhhuff9gkq7pgff5zwv7p
I know that I am wrong at point 5 after testing the seed words at https://www.bip32.net/#english and selecting Derivation path: BIP84
. My program produces the same BIP39 Seed
and BIP32 Root Key
(point 4), but then the "Account Extended Private Key" and/or "BIP32 Extended Private Key" do not match and the created address also doesn't match.
I tried various functions and methods from libbitcoin library but I wasn't able to get to the desired result.
What am I doing wrong in going from the mnemonic to the desired address? Where is the flaw in my understanding?
Thank you very much.
Here's my code so far:
// includes
int main() {
using namespace bc::system;
// convert the mnemonic seed to a binary seed
const auto seed_words =
std::vector<std::string>{"charge", "food", "clutch", "wrap", "crash", "fun",
"age", "celery", "fringe", "electric", "energy", "congress"};
const auto passphrase = std::string{""};
const auto mnemonic = wallet::mnemonic(seed_words, language::en);
const auto seed = mnemonic.to_seed(passphrase);
const auto seed_encoded = encode_base16(seed);
// derive a master private key from the seed
const wallet::context btc_mainnet_p2wpkh{
wallet::prefix::ext::p2wpkh::main::btc,
wallet::prefix::wit::main::btc,
wallet::prefix::add::p2pkh::main::btc,
wallet::prefix::wif::main::btc
};
// m/84/0'
const auto master_private = mnemonic.to_key(passphrase, btc_mainnet_p2wpkh);
std::cout << "master private key: " << master_private << "\n\n";
// ^^ should be correct till now,
// master_private corresponds to "BIP32 Root Key" at https://www.bip32.net/#english
// m/84/0'/0'
// should be account
const auto dpriv = master_private.derive_private(wallet::hd_first_hardened_key | 0);
std::cout << "dpriv: " << dpriv.encoded() << "\n";
// m/84/0'/0'/0
// should be external/internal
const auto ddpriv0 = dpriv.derive_private(0);
std::cout << "ddpriv0: " << ddpriv0.encoded() << "\n";
// m/84/0'/0'/0/0
// should be first public key
const auto first_public = ddpriv0.derive_public(0);
print_addresses(first_public.point());
wallet::witness_address address{first_public.point()};
std::cout << "Witness Address: " << address.encoded() << "\n";
// should be bc1qen0rl0sej2s5s9rhfmhhuff9gkq7pgff5zwv7p
return 0;
}
Hi there! Just assuming a bit too much magic, maybe because of the comments in context.hpp. This should do it for you.
int main()
{
using namespace bc::system;
using namespace bc::system::wallet;
const std::string passphrase{};
const auto words = split(
"charge food clutch wrap crash fun age "
"celery fringe electric energy congress");
const context btc_mainnet_p2wpkh
{
prefix::ext::p2wpkh::main::btc,
prefix::wit::main::btc,
prefix::add::p2pkh::main::btc,
prefix::wif::main::btc
};
// BIP39 Seed.
const mnemonic mnemonic{ words, language::en };
const auto seed = mnemonic.to_seed(passphrase);
std::cout << "seed: " << encode_base16(seed) << std::endl;
// BIP32 Root Key.
const auto m = mnemonic.to_key(passphrase, btc_mainnet_p2wpkh);
std::cout << "m: " << m << std::endl;
// m/84'
// Purpose.
const auto m84h = m.derive_private(84 + hd_first_hardened_key);
std::cout << "m84h: " << m84h << std::endl;
// m/84'/0'
// Coin Type.
const auto m84h0h = m84h.derive_private(0 + hd_first_hardened_key);
std::cout << "m84h0h: " << m84h0h << std::endl;
// m/84'/0'/0'
// Account Extended Private Key.
const auto m84h0h0h = m84h0h.derive_private(0 + hd_first_hardened_key);
std::cout << "m84h0h0h: " << m84h0h0h.encoded() << std::endl;
// m/84'/0'/0'/0
// BIP32 Extended Private Key.
const auto m84h0h0h0 = m84h0h0h.derive_private(0);
std::cout << "m84h0h0h0: " << m84h0h0h0.encoded() << std::endl;
// m/84'/0'/0'/0/0
// First public key.
const auto m84h0h0h00_pub = m84h0h0h0.derive_public(0);
std::cout << "m84h0h0h00_pub: " << m84h0h0h00_pub.encoded() << std::endl;
// Address of first public key.
const witness_address address{ m84h0h0h00_pub.point() };
std::cout << "address: " << address.encoded() << std::endl;
// seed: 0e64d803db7c9242d73a562d0e6849883355f1324e4a880b9c3d75b1eed50ad6407001ba2634a30f319221dad70676e1fbf8b79cee30f8b2c78bad26d162537c
// m: zprvAWgYBBk7JR8GigGRB9yBbeV1n2P6HokWdmNnvoZokZAxbtQNoNk3cALcZ7FsqPnTSEPDVSW62MtujeXwKcctbdHoLPoq9FxX7XrZGy1maTM
// m84h: zprvAZAHTMS37XqeijpFrSfTy3No7UU4fzE8nh9n983vqRhAjfYxTF2si9mvLGfG5j2qkrnSKJFQEYmXupXris81xYyTasxt8LSQtJzr9rahvUw
// m84h0h: zprvAac7gFy9mtqnPWYEFC4Wp5QxZrKYsJTFDi2eJsgK2LUxwb5vcWDq6SGzivGB9Pi6mgSQ6zAj3PMSatzQT7frz5wUKjVmwftu42eXoV4uges
// m84h0h0h: zprvAdGhvKUyvo7Py3jbjWTnh1RyAys8ngCoj39NDJ9FN5xCMX89xDkzyfZ2vvvohdnPD2ERQvFmpeyR17gy1bXe4R7KuyZvGyzMUtEhWzu9x8K
// m84h0h0h0: zprvAeodwWraH5jgJbksLpqcdd7mrwhsXRaNCvseYXJKjg2TThNYgrJxLnBk83rUBYL6kkPkyiUkYtPMS5pZ5SDWE38Mm8CHviSg4Y84LmP3oxV
// m84h0h0h00_pub: zpub6uTTapKRkghMj2mF2CBDFpFxiUgGetNqZNxq63tFSaUAGxULPvdjavAAQw3VhrnGb4mao24DyosF2zeh9euvQ1Tf6dN9vpudr5ACT1xaWS4
// address: bc1qen0rl0sej2s5s9rhfmhhuff9gkq7pgff5zwv7p
return 0;
}
Great! Thank you very much.
I'm closing the ticket.
No problem. The clear write up helps.