A simple example how to use libwally - from generation of the recovery phrase to signing of the psbt transaction
Diff: main.cpp
- Revision:
- 0:3729443d0bf8
- Child:
- 3:3d6e031ab07b
diff -r 000000000000 -r 3729443d0bf8 main.cpp --- /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