A simple example how to use libwally - from generation of the recovery phrase to signing of the psbt transaction

main.cpp

Committer:
diybitcoinhardware
Date:
2019-09-19
Revision:
3:3d6e031ab07b
Parent:
0:3729443d0bf8

File content as of revision 3:3d6e031ab07b:

#include "mbed.h"

#include "wally_core.h"
#include "wally_bip32.h"
#include "wally_bip39.h"
#include "wally_address.h"

#include "wally_psbt.h"
#include "wally_script.h"

RawSerial pc(SERIAL_TX, SERIAL_RX, 115200);

// small helper functions that prints 
// data in hex to the serial port
void print_hex(const uint8_t * data, size_t data_len){
    for(int i=0; i<data_len; i++){
        printf("%02x", data[i]);
    }
}
// just adds a new line to the end of the data
void println_hex(const uint8_t * data, size_t data_len){
    print_hex(data, data_len);
    printf("\r\n");
}
// prints error and hangs forever
void err(const char * message, void * data = NULL){
    printf("ERROR: %s\r\n", message);
    while(1){
        wait(1);
    }
}

void print_psbt(const wally_psbt * psbt);

void libwally_example(){
    size_t len; // to store length of serialization etc
    int res;    // to store wally's results

    // we need to initialize libwally first
    res = wally_init(0);

    /**************** BIP-39 recovery phrase ******************/

    // random buffer should be generated by TRNG or somehow else
    // but we will use predefined one for demo purposes
    // 16 bytes will generate a 12-word recovery phrase
    uint8_t rnd[] = {
        0xbd, 0xb5, 0x1a, 0x16, 0xeb, 0x64, 0x60, 0xec, 
        0x16, 0xf8, 0x4d, 0x7b, 0x6f, 0x19, 0xe2, 0x0d
    };

    // creating a recovery phrase
    char *phrase = NULL;
    res = bip39_mnemonic_from_bytes(NULL, rnd, sizeof(rnd), &phrase);
    printf("Recovery phrase: %s\r\n", phrase);

    // converting recovery phrase to seed
    uint8_t seed[BIP39_SEED_LEN_512];
    res = bip39_mnemonic_to_seed(phrase, "my password", seed, sizeof(seed), &len);

    // don't forget to securely clean up the string when done
    wally_free_string(phrase);

    printf("Seed: ");
    println_hex(seed, sizeof(seed));

    /**************** BIP-32 HD keys ******************/

    // root HD key
    ext_key * root = NULL;
    res = bip32_key_from_seed_alloc(seed, sizeof(seed), BIP32_VER_TEST_PRIVATE, 0, &root);
    // get base58 xprv string
    char *xprv = NULL;
    res = bip32_key_to_base58(root, BIP32_FLAG_KEY_PRIVATE, &xprv);
    printf("Root key: %s\r\n", xprv);
    // don't forget to securely clean up the string when done
    wally_free_string(xprv);

    // deriving account key for native segwit, testnet: m/84h/1h/0h
    ext_key * account = NULL;
    uint32_t path[] = {
        BIP32_INITIAL_HARDENED_CHILD+84, // 84h
        BIP32_INITIAL_HARDENED_CHILD+1,  // 1h
        BIP32_INITIAL_HARDENED_CHILD     // 0h
    };
    res = bip32_key_from_parent_path_alloc(root, path, 3, BIP32_FLAG_KEY_PRIVATE, &account);

    res = bip32_key_to_base58(account, BIP32_FLAG_KEY_PRIVATE, &xprv);
    printf("Account private key: %s\r\n", xprv);
    // don't forget to securely clean up the string when done
    wally_free_string(xprv);

    char *xpub = NULL;
    res = bip32_key_to_base58(account, BIP32_FLAG_KEY_PUBLIC, &xpub);
    printf("Account public key: %s\r\n", xpub);

    // fingerprint
    printf("Derivation information: [");
    print_hex(root->hash160, 4);
    printf("/84h/1h/0h]%s\r\n", xpub);

    // don't forget to securely clean up the string when done
    wally_free_string(xpub);

    /**************** Addresses ******************/

    // key for the first address
    ext_key * first_recv = NULL;
    uint32_t recv_path[] = {0, 0};
    // we only need public key here, no need in private key
    res = bip32_key_from_parent_path_alloc(account, recv_path, 2, BIP32_FLAG_KEY_PUBLIC, &first_recv);
    char * addr = NULL;

    // native segwit address
    res = wally_bip32_key_to_addr_segwit(first_recv, "tb", 0, &addr);
    printf("Segwit address: %s\r\n", addr);
    wally_free_string(addr);

    // nested segwit address
    res = wally_bip32_key_to_address(first_recv, WALLY_ADDRESS_TYPE_P2SH_P2WPKH, WALLY_ADDRESS_VERSION_P2SH_TESTNET, &addr);
    printf("Nested segwit address: %s\r\n", addr);
    wally_free_string(addr);

    // legacy address
    res = wally_bip32_key_to_address(first_recv, WALLY_ADDRESS_TYPE_P2PKH, WALLY_ADDRESS_VERSION_P2PKH_TESTNET, &addr);
    printf("Legacy address: %s\r\n", addr);
    wally_free_string(addr);

    /**************** PSBT ******************/

    char b64_psbt[] = "cHNidP8BAHICAAAAAX7U8W8nyYJcYzFjoHa5UH5j0Ug43ej/q2bf"
                          "IPq8XnjeAAAAAAD/////AihATAAAAAAAFgAUPE9Ix5e2qWWcDW78"
                          "DM+uqX09ANNAS0wAAAAAABepFPPNvp1TAyho+Kys4wwejY8iakgQ"
                          "hwAAAAAAAQEfgJaYAAAAAAAWABRy2Nt/+F2j9EYd2bcI5CfSqeey"
                          "MSIGAmijc35Kh8Nfa9oC0jGb2I1UfzkS0MqAfHw0BKA6JRwuGCbd"
                          "2XhUAACAAQAAgAAAAIAAAAAAAAAAAAAiAgOTSWH+F+K8DsY9lzWM"
                          "QZJptjBUmMVgdE+1814LZMwVFRgm3dl4VAAAgAEAAIAAAACAAQAAAAAAAAAAAA==";
    wally_psbt * psbt = NULL;
    res = wally_psbt_from_base64(b64_psbt, &psbt);

    print_psbt(psbt);

    for(int i = 0; i < psbt->num_inputs; i++){
        if(!psbt->inputs[i].witness_utxo){
            printf("Too lazy for legacy");
            return;
        }
        uint8_t hash[32];
        uint8_t script[25];
        printf("scriptpubkey: ");
        println_hex(psbt->inputs[i].witness_utxo->script, psbt->inputs[i].witness_utxo->script_len);
        wally_scriptpubkey_p2pkh_from_bytes(
            psbt->inputs[i].witness_utxo->script+2, 20,
            0,
            script, 25, &len);
        printf("scriptpubkey: ");
        println_hex(script, len);
        wally_tx_get_btc_signature_hash(psbt->tx, i, 
                script, len,
                psbt->inputs[i].witness_utxo->satoshi,
                WALLY_SIGHASH_ALL,
                WALLY_TX_FLAG_USE_WITNESS,
                hash, 32
            );
        printf("Input %d. Hash to sign: ", i);
        println_hex(hash, 32);
        ext_key * pk = NULL;
        bip32_key_from_parent_path_alloc(root, 
            psbt->inputs[i].keypaths->items[0].origin.path, 
            psbt->inputs[i].keypaths->items[0].origin.path_len, 
            BIP32_FLAG_KEY_PRIVATE, &pk);

        uint8_t sig[EC_SIGNATURE_LEN];
        wally_ec_sig_from_bytes(
                pk->priv_key+1, 32, // first byte of ext_key.priv_key is 0x00
                hash, 32,
                EC_FLAG_ECDSA,
                sig, EC_SIGNATURE_LEN
            );
        bip32_key_free(pk);
        uint8_t der[EC_SIGNATURE_DER_MAX_LEN+1];
        wally_ec_sig_to_der(
                sig, EC_SIGNATURE_LEN,
                der, EC_SIGNATURE_DER_MAX_LEN,
                &len
            );
        der[len] = WALLY_SIGHASH_ALL;
        if(!psbt->inputs[i].partial_sigs){
            partial_sigs_map_init_alloc(1, &psbt->inputs[i].partial_sigs);
        }
        add_new_partial_sig(psbt->inputs[i].partial_sigs,
                pk->pub_key, 
                der, len+1
            );
    }

    char * output;
    wally_psbt_to_base64(psbt, &output);
    wally_psbt_free(psbt);

    printf("PSBT: %s\r\n", output);
    wally_free_string(output);

    bip32_key_free(root);
    bip32_key_free(account);
    wally_cleanup(0);
}

void print_psbt(const wally_psbt * psbt){
    printf("PSBT:\r\n"
           "Inputs: %d\r\n", psbt->num_inputs);
    uint64_t in_amount = 0;
    for(int i = 0; i < psbt->num_inputs; i++){
        if(!psbt->inputs[i].witness_utxo){
            printf("Non-segwit input or prev output is missing! It's bad...");
            return;
        }
        in_amount += psbt->inputs[i].witness_utxo->satoshi;
    }
    printf("Spending %llu satoshi\r\n", in_amount);
    printf("Outputs: %d\r\n", psbt->tx->num_outputs);
    uint64_t out_amount = 0;
    for(int i=0; i < psbt->tx->num_outputs; i++){
        size_t script_type;
        wally_scriptpubkey_get_type(psbt->tx->outputs[i].script, psbt->tx->outputs[i].script_len, &script_type);
        char * addr = NULL;
        uint8_t bytes[21];
        // should deal with all script types, only P2WPKH for now
        switch(script_type){
            case WALLY_SCRIPT_TYPE_P2WPKH:
            case WALLY_SCRIPT_TYPE_P2WSH:
                wally_addr_segwit_from_bytes(psbt->tx->outputs[i].script, psbt->tx->outputs[i].script_len, "tb", 0, &addr);
                break;
            case WALLY_SCRIPT_TYPE_P2SH:
                bytes[0] = WALLY_ADDRESS_VERSION_P2SH_TESTNET;
                memcpy(bytes+1, psbt->tx->outputs[i].script+2, 20);
                wally_base58_from_bytes(bytes, 21, BASE58_FLAG_CHECKSUM, &addr);
                break;
            case WALLY_SCRIPT_TYPE_P2PKH:
                bytes[0] = WALLY_ADDRESS_VERSION_P2PKH_TESTNET;
                memcpy(bytes+1, psbt->tx->outputs[i].script+3, 20);
                wally_base58_from_bytes(bytes, 21, BASE58_FLAG_CHECKSUM, &addr);
                break;
        }
        // TODO: also verify if it is a change address
        if(addr){
            printf("- Out %d: %llu sat to %s\r\n", i+1, psbt->tx->outputs[i].satoshi, addr);
            wally_free_string(addr);
        }else{
            printf("- Out %d: %llu sat to %s\r\n", i+1, psbt->tx->outputs[i].satoshi, "...custom script...");
        }
        out_amount += psbt->tx->outputs[i].satoshi;
    }
    printf("Fee: %llu sat\r\n", in_amount-out_amount);
}

int main(){
    printf("Press any key to continue\r\n");
    pc.getc();
    printf("Ready to go!\r\n");

    printf("=== Running example for libwally ===\r\n");
    libwally_example();

    printf("\r\n=== Done ===\r\n");

}