Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
349 lines (303 sloc) 10.3 KB
#include "wally_core.h"
#include "wally_bip32.h"
#include "wally_bip39.h"
#include "wally_address.h"
#include "wally_psbt.h"
#include "wally_script.h"
// to implement println_b64
#include "utility/ccan/ccan/base64/base64.h"
// forward declaration
void err(const char * message, void * data = NULL);
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial.println("Press any key to continue.");
while(!Serial.available()){
delay(100);
}
Serial.println("\nOk, let's rock!");
wally_example();
Serial.println("=== Done ===");
}
void loop() {
delay(1000);
}
void wally_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);
// root HD key
ext_key * root = NULL;
// derives hd key from mnemonic and account key from root
derive_keys(&root);
// prints addresses: native segwit, nested segwit and legacy
print_addresses(root);
// psbt to sign
const char b64_psbt[] = "cHNidP8BAHICAAAAAX7U8W8nyYJcYzFjoHa5UH5j0Ug43ej/q2bf"
"IPq8XnjeAAAAAAD/////AihATAAAAAAAFgAUPE9Ix5e2qWWcDW78"
"DM+uqX09ANNAS0wAAAAAABepFPPNvp1TAyho+Kys4wwejY8iakgQ"
"hwAAAAAAAQEfgJaYAAAAAAAWABRy2Nt/+F2j9EYd2bcI5CfSqeey"
"MSIGAmijc35Kh8Nfa9oC0jGb2I1UfzkS0MqAfHw0BKA6JRwuGCbd"
"2XhUAACAAQAAgAAAAIAAAAAAAAAAAAAiAgOTSWH+F+K8DsY9lzWM"
"QZJptjBUmMVgdE+1814LZMwVFRgm3dl4VAAAgAEAAIAAAACAAQAAAAAAAAAAAA==";
// displays psbt inputs and outputs,
// also signes with root key and prints signed tx
parse_sign_psbt(b64_psbt, root);
wally_cleanup(0);
}
void derive_keys(ext_key ** root){
int res;
size_t len;
/**************** 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);
Serial.print("Recovery phrase: ");
Serial.println(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);
Serial.print("Seed: ");
println_hex(seed, sizeof(seed));
/**************** BIP-32 HD keys ******************/
// allocates the memory for the key and points root to it
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);
Serial.print("Root key: ");
Serial.println(xprv);
// don't forget to securely clean up the string when done
wally_free_string(xprv);
}
void print_addresses(const ext_key * root){
int res;
// 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);
char *xprv = NULL;
res = bip32_key_to_base58(account, BIP32_FLAG_KEY_PRIVATE, &xprv);
Serial.print("Account private key: ");
Serial.println(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);
Serial.print("Account public key: ");
Serial.println(xpub);
// fingerprint
Serial.print("Derivation information: [");
print_hex(root->hash160, 4);
Serial.print("/84h/1h/0h]");
Serial.println(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);
// we don't need account anymore - free it
bip32_key_free(account);
char * addr = NULL;
// native segwit address
res = wally_bip32_key_to_addr_segwit(first_recv, "tb", 0, &addr);
Serial.print("Segwit address: ");
Serial.println(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);
Serial.print("Nested segwit address: ");
Serial.println(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);
Serial.print("Legacy address: ");
Serial.println(addr);
wally_free_string(addr);
// free first recv key
bip32_key_free(first_recv);
}
void parse_sign_psbt(const char * b64_psbt, const ext_key * root){
int res;
size_t len;
/**************** PSBT ******************/
wally_psbt * psbt = NULL;
res = wally_psbt_from_base64(b64_psbt, &psbt);
print_psbt(psbt);
// signing
for(int i = 0; i < psbt->num_inputs; i++){
sign_psbt_input(psbt, i, root);
}
psbt_get_length(psbt, &len);
uint8_t out[500]; // should be enough in this case
res = wally_psbt_to_bytes(psbt, out, sizeof(out), &len);
Serial.println("Signed transaction:");
println_b64((char *)out, len);
}
void sign_psbt_input(wally_psbt * psbt, int i, const ext_key * root){
size_t len = 0;
if(!psbt->inputs[i].witness_utxo){
Serial.println("Too lazy for legacy");
return;
}
uint8_t hash[32];
uint8_t script[25];
wally_scriptpubkey_p2pkh_from_bytes(
psbt->inputs[i].witness_utxo->script+2, 20,
0,
script, 25, &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
);
Serial.print("Input ");
Serial.print(i);
Serial.print(": hash to sign: ");
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
);
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
);
}
void print_psbt(const wally_psbt * psbt){
Serial.println("PSBT:");
Serial.print("Inputs: ");
Serial.println(psbt->num_inputs);
uint64_t in_amount = 0;
for(int i = 0; i < psbt->num_inputs; i++){
if(!psbt->inputs[i].witness_utxo){
Serial.println("Non-segwit input or prev output is missing! It's bad...");
return;
}
in_amount += psbt->inputs[i].witness_utxo->satoshi;
}
Serial.print("Spending ");
Serial.print((uint32_t)in_amount);
Serial.println(" satoshi");
Serial.print("Outputs: ");
Serial.println(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
Serial.print("- Out ");
Serial.print(i+1);
Serial.print(": ");
// Print doesn't support u64, so we can either convert it
// to float or use u32
Serial.print((uint32_t)psbt->tx->outputs[i].satoshi);
Serial.print(" sat to ");
if(addr){
Serial.println(addr);
wally_free_string(addr);
}else{
Serial.println("...custom script...");
}
out_amount += psbt->tx->outputs[i].satoshi;
}
Serial.print("Fee: ");
Serial.print((uint32_t)(in_amount-out_amount));
Serial.println(" sat");
}
void print_hex(const uint8_t * data, size_t data_len){
char arr[3];
for(int i=0; i<data_len; i++){
if(data[i]<0x10){ Serial.print("0"); }
Serial.print(data[i], HEX);
}
}
// 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);
Serial.println();
}
// prints in base64 piece by piece => no need for huge malloc
void println_b64(const char * data, size_t data_len){
char s[5]=""; // +1 for \0
size_t len;
// we can encode base64 3 bytes at a time
for(int i=0; i<data_len; i+=3){
len = 3;
if(data_len-i >= 3){
base64_encode_triplet(s, (data+i));
}else{
if(i>=data_len){
return;
}else{
base64_encode_tail(s, (data+i), data_len-i);
}
}
Serial.print(s);
}
Serial.println();
}
// prints error and hangs forever
void err(const char * message, void * data){
Serial.print("ERROR: ");
Serial.println(message);
while(1){
delay(1000);
}
}
You can’t perform that action at this time.