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

Revision:
0:3729443d0bf8
Child:
3:3d6e031ab07b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Sun Sep 15 20:33:30 2019 +0000
@@ -0,0 +1,263 @@
+#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;
+    res = bip32_key_from_seed(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;
+    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(&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;
+    uint32_t recv_path[] = {0, 0};
+    // we only need public key here, no need in private key
+    res = bip32_key_from_parent_path(&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[] = "cHNidP8BAHICAAAAAZ6vI6XXFrVMCyCnY0pmGQkwTsHi/kNK0dQmMZj+JqsiAAAAA"
+                      "AD/////AlDDAAAAAAAAF6kUTrBkI8NYr13XpG4aVhwsUgkq+HaHwsIAAAAAAAAWAB"
+                      "Q8T0jHl7apZZwNbvwMz66pfT0A0wAAAAAAAQEfoIYBAAAAAAAWABRy2Nt/+F2j9EY"
+                      "d2bcI5CfSqeeyMSIGAmijc35Kh8Nfa9oC0jGb2I1UfzkS0MqAfHw0BKA6JRwuGCbd"
+                      "2XhUAACAAQAAgAAAAIAAAAAAAAAAAAAAIgIDk0lh/hfivA7GPZc1jEGSabYwVJjFY"
+                      "HRPtfNeC2TMFRUYJt3ZeFQAAIABAACAAAAAgAEAAAAAAAAAAA==";
+    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;
+        bip32_key_from_parent_path(&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
+            );
+        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);
+
+    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");
+
+}
\ No newline at end of file