diff --git a/Makefile b/Makefile index 67c8d0639..ad7ed563e 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ FEATURES := -DHAS_CSV=1 -DALPHA_TXSTYLE=1 -DUSE_SCHNORR=1 -DHAS_CLTV=1 # Bitcoin uses DER for signatures #FEATURES := -DSCRIPTS_USE_DER -PROGRAMS := test-cli/open-channel test-cli/create-anchor-tx test-cli/open-commit-sig test-cli/check-commit-sig test-cli/get-anchor-depth test-cli/create-steal-tx test-cli/create-commit-spend-tx test-cli/close-channel test-cli/create-close-tx test-cli/update-channel test-cli/update-channel-accept test-cli/update-channel-signature test-cli/update-channel-complete test-cli/create-commit-tx test-cli/txid-of test-cli/open-anchor test-cli/update-channel-htlc test-cli/update-channel-htlc-complete test-cli/update-channel-htlc-remove +PROGRAMS := test-cli/open-channel test-cli/create-anchor-tx test-cli/open-commit-sig test-cli/check-commit-sig test-cli/get-anchor-depth test-cli/create-steal-tx test-cli/create-commit-spend-tx test-cli/close-channel test-cli/create-close-tx test-cli/update-channel test-cli/update-channel-accept test-cli/update-channel-signature test-cli/update-channel-complete test-cli/create-commit-tx test-cli/txid-of test-cli/open-anchor test-cli/update-channel-htlc test-cli/update-channel-htlc-complete test-cli/update-channel-htlc-remove test-cli/create-htlc-spend-tx BITCOIN_OBJS := bitcoin/address.o bitcoin/base58.o bitcoin/pubkey.o bitcoin/script.o bitcoin/shadouble.o bitcoin/signature.o bitcoin/tx.o diff --git a/test-cli/.gitignore b/test-cli/.gitignore index 19b89121a..105669531 100644 --- a/test-cli/.gitignore +++ b/test-cli/.gitignore @@ -20,4 +20,5 @@ create-anchor-tx update-channel-htlc update-channel-htlc-complete update-channel-htlc-remove +create-htlc-spend-tx diff --git a/test-cli/create-htlc-spend-tx.c b/test-cli/create-htlc-spend-tx.c new file mode 100644 index 000000000..06dc4675f --- /dev/null +++ b/test-cli/create-htlc-spend-tx.c @@ -0,0 +1,206 @@ +#include +#include +#include +#include +#include +#include +#include +#include "lightning.pb-c.h" +#include "bitcoin/base58.h" +#include "pkt.h" +#include "bitcoin/script.h" +#include "permute_tx.h" +#include "bitcoin/signature.h" +#include "commit_tx.h" +#include "bitcoin/pubkey.h" +#include "bitcoin/privkey.h" +#include "protobuf_convert.h" +#include "find_p2sh_out.h" +#include + +int main(int argc, char *argv[]) +{ + const tal_t *ctx = tal_arr(NULL, char, 0); + struct sha256 revoke_hash, htlc_rhash, val, expect; + OpenChannel *o1, *o2; + UpdateAddHtlc *u; + struct bitcoin_tx *commit, *tx; + u8 *redeemscript; + struct pubkey pubkey1, pubkey2, key, outpubkey; + struct bitcoin_signature sig; + struct privkey privkey; + bool testnet; + u32 locktime, htlc_abstimeout; + char *rvalue = NULL, *preimage = NULL; + bool received, own_commit_tx; + Pkt *pkt; + const void *secret = NULL; + size_t secret_len = 0; + + err_set_progname(argv[0]); + + opt_register_arg("--rvalue", opt_set_charp, NULL, &rvalue, + "Use R value to spend htlc output"); + opt_register_arg("--commit-preimage=", opt_set_charp, NULL, &preimage, + "Use commit revocation preimage to spend htlc output"); + opt_register_noarg("--help|-h", opt_usage_and_exit, + " [+-] \n" + "Create a transaction which spends commit-tx's htlc output, and sends it P2SH to outpubkey\n" + "It relies on timeout, unless --rvalue or --commit-preimage is specified", + "Print this message."); + + opt_parse(&argc, argv, opt_log_stderr_exit); + + if (argc != 8) + opt_usage_exit_fail("Expected 7 arguments"); + + o1 = pkt_from_file(argv[1], PKT__PKT_OPEN)->open; + o2 = pkt_from_file(argv[2], PKT__PKT_OPEN)->open; + commit = bitcoin_tx_from_file(ctx, argv[3]); + if (strstarts(argv[4], "+")) + received = false; + else if (strstarts(argv[4], "-")) + received = true; + else + errx(1, "%s doesn't begin with + or -", argv[4]); + u = pkt_from_file(argv[4]+1, PKT__PKT_UPDATE_ADD_HTLC)->update_add_htlc; + + /* This gives us the revocation hash. */ + pkt = any_pkt_from_file(argv[5]); + switch (pkt->pkt_case) { + case PKT__PKT_UPDATE_ADD_HTLC: + proto_to_sha256(pkt->update_add_htlc->revocation_hash, + &revoke_hash); + break; + case PKT__PKT_UPDATE: + proto_to_sha256(pkt->update->revocation_hash, &revoke_hash); + break; + case PKT__PKT_UPDATE_ACCEPT: + proto_to_sha256(pkt->update_accept->revocation_hash, + &revoke_hash); + break; + default: + errx(1, "Expected update or update-add-htlc for %s", argv[5]); + } + + if (!key_from_base58(argv[6], strlen(argv[6]), &testnet, &privkey, &key)) + errx(1, "Invalid private key '%s'", argv[6]); + if (!testnet) + errx(1, "Private key '%s' not on testnet!", argv[6]); + + if (!pubkey_from_hexstr(argv[7], &outpubkey)) + errx(1, "Invalid commit key '%s'", argv[7]); + + /* Get pubkeys */ + if (!proto_to_pubkey(o1->final_key, &pubkey1)) + errx(1, "Invalid o1 final pubkey"); + if (!proto_to_pubkey(o2->final_key, &pubkey2)) + errx(1, "Invalid o2 final pubkey"); + + if (pubkey_len(&key) == pubkey_len(&pubkey1) + && memcmp(key.key, pubkey1.key, pubkey_len(&pubkey1)) == 0) { + own_commit_tx = true; + } else if (pubkey_len(&key) == pubkey_len(&pubkey2) + && memcmp(key.key, pubkey2.key, pubkey_len(&pubkey2)) == 0) { + own_commit_tx = false; + } else + errx(1, "Privkey doesn't match either key"); + + if (!proto_to_rel_locktime(o2->delay, &locktime)) + errx(1, "Invalid o2 delay"); + if (!proto_to_abs_locktime(u->expiry, &htlc_abstimeout)) + errx(1, "Invalid htlc expiry"); + proto_to_sha256(u->r_hash, &htlc_rhash); + + if (received) { + redeemscript = scriptpubkey_htlc_recv(ctx, &pubkey1, &pubkey2, + u->amount, htlc_abstimeout, + locktime, &revoke_hash, + &htlc_rhash); + } else { + redeemscript = scriptpubkey_htlc_send(ctx, &pubkey1, &pubkey2, + u->amount, htlc_abstimeout, + locktime, &revoke_hash, + &htlc_rhash); + } + + if (rvalue) { + if (!hex_decode(rvalue, strlen(rvalue), &val, sizeof(val))) + errx(1, "Invalid rvalue '%s' - need 256 hex bits", + rvalue); + sha256(&expect, &val, sizeof(val)); + if (!structeq(&expect, &htlc_rhash)) + errx(1, "--rvalue is not correct"); + secret = &val; + secret_len = sizeof(val); + } + if (preimage) { + Pkt *pkt = any_pkt_from_file(preimage); + switch (pkt->pkt_case) { + case PKT__PKT_UPDATE_SIGNATURE: + proto_to_sha256(pkt->update_signature->revocation_preimage, + &val); + break; + case PKT__PKT_UPDATE_COMPLETE: + proto_to_sha256(pkt->update_complete->revocation_preimage, + &val); + break; + default: + errx(1, "Expected update or update-complete in %s", + preimage); + } + sha256(&expect, &val, sizeof(val)); + if (!structeq(&expect, &revoke_hash)) + errx(1, "--commit-preimage is not correct"); + secret = &val; + secret_len = sizeof(val); + } + + tx = bitcoin_tx(ctx, 1, 1); + bitcoin_txid(commit, &tx->input[0].txid); + tx->input[0].index = find_p2sh_out(commit, redeemscript); + tx->input[0].input_amount = commit->output[tx->input[0].index].amount; + + if (!secret_len) { + /* We must be relying on HTLC timeout. */ + tx->lock_time = htlc_abstimeout; + /* Locktime only applies if an input has seq != ffffffff... */ + tx->input[0].sequence_number = 0; + } + + /* If it's our own commit tx, we also need delay. */ + if (own_commit_tx) { + /* Sequence number is inverted timeout. */ + tx->input[0].sequence_number = ~locktime; + } + + /* Leave 10,000 satoshi as fee (if we can!). */ + tx->fee = 10000; + if (tx->input[0].input_amount <= tx->fee) + errx(1, "Cannot afford fee: only %llu satoshi!", + (long long)tx->input[0].input_amount); + tx->output[0].amount = tx->input[0].input_amount - tx->fee; + tx->output[0].script = scriptpubkey_p2sh(tx, + bitcoin_redeem_single(tx, &outpubkey)); + tx->output[0].script_length = tal_count(tx->output[0].script); + + /* Now get signature, to set up input script. */ + if (!sign_tx_input(tx, tx, 0, redeemscript, tal_count(redeemscript), + &privkey, &key, &sig.sig)) + errx(1, "Could not sign tx"); + + sig.stype = SIGHASH_ALL; + tx->input[0].script = scriptsig_p2sh_secret(tx, secret, secret_len, + &sig, + redeemscript, + tal_count(redeemscript)); + tx->input[0].script_length = tal_count(tx->input[0].script); + + /* Print it out in hex. */ + if (!bitcoin_tx_write(STDOUT_FILENO, tx)) + err(1, "Writing out transaction"); + + tal_free(ctx); + return 0; +} +