libbitcoin / libbitcoin-system

Bitcoin Cross-Platform C++ Development Toolkit

Home Page:https://libbitcoin.info/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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:

  1. initialize testing seed words: charge food clutch wrap crash fun age celery fringe electric energy congress and no passphrase
  2. create a mnemonic object from the seed words
  3. create a context object for p2wpkh
  4. create a master private key from mnemonic and context (if I get it right, we are now at m/84'/0', i.e. purpose and coin)
  5. derive hardened private key from the master private key (to get to m/84'/0'/0', i.e. primary account)
  6. derive normal private key from the key from previous point (to get to m/84'/0'/0'/0, i.e. not change)
  7. 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)
  8. creating a witness_address object from the key from previous point, I expect the output to be bc1qen0rl0sej2s5s9rhfmhhuff9gkq7pgff5zwv7p

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.