ElementsProject / libwally-core

Useful primitives for wallets
Other
280 stars 134 forks source link

Help wanted with signing a native segwit transactions #425

Closed TorgaW closed 9 months ago

TorgaW commented 9 months ago

I'm trying to create a bitcoin wallet for educational purpose and want to implement Segwit support.\ Currently I'm working with Testnet.\ I have done already wallet private key generation with bip32_key_from_seed() function:

/*entropy is a random byte array*/
/*hdkey is a ext_key struct*/
bip32_key_from_seed(entropy, 32, BIP32_VER_TEST_PRIVATE, 0u, &hdkey);

After private key generation I'm creating SegWit address:

/*hdkey with private key*/
/*t__ is char* to write address in*/
wally_bip32_key_to_addr_segwit(&hdkey, "tb", 0, &(t__));

For example we have this address: tb1qsrfp2j0qgtcv767lu5rzxjgw3lz444jet00mu9\ After that I'm generating 2 more addresses: change and recepient\ \ Change: tb1qh5vxl5xhzc2vqhs7kag8hugzcw7w86kvc855ar\ Recepient: tb1qep9mf5377l2xxv8lle38hfshjjr6xxnhvl0gra\ \ I have sent some testnet bitcoins to tb1qsrfp2j0qgtcv767lu5rzxjgw3lz444jet00mu9 and got UTXO:

50cefed1db2526a2945bf925713d52038fc4277106519230af101adb38ea1d63

Creating a new transaction

I'm starting with creation of wally_tx_input struct:

byte utxo_hash_bytes[32];
wally_hex_to_bytes("50cefed1db2526a2945bf925713d52038fc4277106519230af101adb38ea1d63", utxo_hash_bytes, 32, nullptr);
std::reverse(std::begin(utxo_hash_bytes), std::end(utxo_hash_bytes));

wally_tx_input *new_tx_input;

/*vout index is 1*/
wally_tx_input_init_alloc(utxo_hash_bytes, WALLY_TXHASH_LEN, (uint64_t)1, 0xffffffff, nullptr, 0, nullptr, &new_tx_input);

Then two outputs:

uint64_t amount = 25000;

wally_tx_output *new_tx_output0;
/*
w1.receive_addresses[0].as_bytes is a result of function 
wally_addr_segwit_to_bytes() from address tb1qep9mf5377l2xxv8lle38hfshjjr6xxnhvl0gra
*/
wally_tx_output_init_alloc(amount, w1.receive_addresses[0].as_bytes, WALLY_SCRIPTPUBKEY_P2WPKH_LEN, &new_tx_output0);

wally_tx_output *new_tx_output1;
/*
w0.change_addresses[0].as_bytes is a result of function 
wally_addr_segwit_to_bytes() from address tb1qh5vxl5xhzc2vqhs7kag8hugzcw7w86kvc855ar
*/
wally_tx_output_init_alloc(5000, w0.change_addresses[0].as_bytes, WALLY_SCRIPTPUBKEY_P2WPKH_LEN, &new_tx_output1);

After inputs and outputs creation I'm creating wally_tx struct and fill with inputs and outputs:

wally_tx *new_tx;
wally_tx_init_alloc(WALLY_TX_VERSION_1, 0, 1, 2, &new_tx);
wally_tx_add_input(new_tx, new_tx_input);
wally_tx_add_output(new_tx, new_tx_output0);
wally_tx_add_output(new_tx, new_tx_output1);

Finally, prepare for signing:

byte btc_sighash[SHA256_LEN];

//creating scriptCode for native SegWit
byte sig_input_script[26];
sig_input_script[0] = 0x19;
sig_input_script[1] = 0x76;
sig_input_script[2] = 0xA9;
sig_input_script[3] = 0x14;
//copy pubKeyHash
memcpy(&sig_input_script[4], w0.receive_addresses[0].as_bytes + 2, 20);
sig_input_script[24] = 0x88;
sig_input_script[25] = 0xAC;

wally_tx_get_btc_signature_hash(new_tx, 0, sig_input_script, 26,
                                amount, WALLY_SIGHASH_ALL, WALLY_TX_FLAG_USE_WITNESS, 
                                btc_sighash, SHA256_LEN);

Sign and set witness stack:

byte signature[EC_SIGNATURE_LEN];
wally_ec_sig_from_bytes(w0.receive_addresses[0].keypair.priv_key + 1, EC_PRIVATE_KEY_LEN, btc_sighash, SHA256_LEN, 
                        EC_FLAG_ECDSA, signature, EC_SIGNATURE_LEN);

byte der_signature[EC_SIGNATURE_DER_MAX_LEN+1];
size_t der_len = 0;
wally_ec_sig_to_der(signature, EC_SIGNATURE_LEN, der_signature, EC_SIGNATURE_DER_MAX_LEN, &der_len);
der_signature[der_len] = WALLY_SIGHASH_ALL;

wally_tx_witness_stack *witness;
wally_tx_witness_stack_init_alloc(2, &witness);
wally_tx_witness_stack_set(witness, 0, der_signature, der_len+1);
wally_tx_witness_stack_set(witness, 1, w0.receive_addresses[0].keypair.pub_key, EC_PUBLIC_KEY_LEN);
wally_tx_set_input_witness(new_tx, 0, witness);

After signing I'm converting *new_tx to bytes:

byte raw_tx[8192];
size_t raw_tx_len;
wally_tx_to_bytes(new_tx, WALLY_TX_FLAG_USE_WITNESS, raw_tx, 8192, &raw_tx_len);
char *raw_hex_tx;
wally_hex_from_bytes(raw_tx, raw_tx_len, &raw_hex_tx);
//print raw transaction
std::cout << "-----RAW TX:\n" << raw_hex_tx << "\n";

And the result is:

01000000000101631dea38db1a10af309251067127c48f03523d7125f95b94a22625dbd1fece500100000000ffffffff02a861000000000000160014c84bb4d23ef7d46330fffe627ba6179487a31a778813000000000000160014bd186fd0d71614c05e1eb7507bf102c3bce3eacc02473044022006550a64787b9b5b9c08330d03d5d2a33ab1b7fd8a14cd4746697aa2d66aa72c022011b4761709ff6a7ac5e51ddf94fe528b80a1513d8f4a223d801672011fc78dcf0121034520378d54b95a6f8678ab8bdcf14b1f94c582d300db1ec72a34358dfdffeee400000000

Broadcast

I'm using blockcypher webpage to broadcast raw transaction in testnet network, but got an error:

Error validating transaction: Error running script for input 0 referencing 50cefed1db2526a2945bf925713d52038fc4277106519230af101adb38ea1d63 at 1: Script was NOT verified successfully..

I have found official BIP143 wiki in bitcoin repo on github, but it didn't help :(\ I really want to know at wich point I made a mistake.

ko-matsu commented 9 months ago

Finally, prepare for signing:

Is the amount you are setting for "wally_tx_get_btc_signature_hash" 25000? If your current UTXO's amount is 30000+(tx fee amount), you should specify 30000+(tx fee amount).

Broadcast

~Also, the transaction fee is not set to transaction output in the first place.~ ~I think cannot broadcast this transaction in this state.~ Sorry, I mistook it for Elements. Don't worry about it here.

ko-matsu commented 9 months ago

byte sig_input_script[26]; sig_input_script[0] = 0x19;

0x19 is script size. please remove from sig_input_script.

byte sig_input_script[25];
sig_input_script[0] = 0x76;
sig_input_script[1] = 0xA9;
sig_input_script[2] = 0x14;
//copy pubKeyHash
memcpy(&sig_input_script[3], w0.receive_addresses[0].as_bytes + 2, 20);
sig_input_script[23] = 0x88;
sig_input_script[24] = 0xAC;

see: https://github.com/ElementsProject/libwally-core/blob/c102e7e8c7899822666e22d6cbcdaa8a1f444d38/src/script.c#L310-L317

should use wally_scriptpubkey_p2pkh_from_bytes .

TorgaW commented 9 months ago

Got it! Broadcasted successfully!

There were 2 problems: 1) You was right with satoshi amount problem! In UTXO I have 35082 value, but put in function only 20000... stupid mistake! 2) Script problem: 0x19 is redundant, a little bit confused why in a moment, but now see a comment The (unprefixed) scriptCode, so got it!

Many thanks to you! I really like wally library, the best bitcoin library for C/C++!

jgriffiths commented 9 months ago

Thanks @ko-matsu !