From 502ff91140cd359ac15366efeddeac180fccb3ea Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Wed, 16 Mar 2022 00:22:39 -0700 Subject: [PATCH 01/53] Add remote_hsmd --- .dir-locals.el | 8 +- .github/scripts/build.sh | 2 + Makefile | 7 +- contrib/remote_hsmd/.gitignore | 5 + contrib/remote_hsmd/Makefile | 124 ++ contrib/remote_hsmd/NOTES.md | 113 ++ contrib/remote_hsmd/TODO.md | 44 + contrib/remote_hsmd/capabilities.h | 12 + contrib/remote_hsmd/dump.cc | 548 ++++++++ contrib/remote_hsmd/dump.hpp | 48 + contrib/remote_hsmd/hsmd.c | 1946 ++++++++++++++++++++++++++++ contrib/remote_hsmd/proxy.cc | 1681 ++++++++++++++++++++++++ contrib/remote_hsmd/proxy.hpp | 231 ++++ external/.gitignore | 3 +- lightningd/.gitignore | 1 + 15 files changed, 4769 insertions(+), 4 deletions(-) create mode 100644 contrib/remote_hsmd/.gitignore create mode 100644 contrib/remote_hsmd/Makefile create mode 100644 contrib/remote_hsmd/NOTES.md create mode 100644 contrib/remote_hsmd/TODO.md create mode 100644 contrib/remote_hsmd/capabilities.h create mode 100644 contrib/remote_hsmd/dump.cc create mode 100644 contrib/remote_hsmd/dump.hpp create mode 100644 contrib/remote_hsmd/hsmd.c create mode 100644 contrib/remote_hsmd/proxy.cc create mode 100644 contrib/remote_hsmd/proxy.hpp diff --git a/.dir-locals.el b/.dir-locals.el index 1ca27d4ff271..518359236abd 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -4,4 +4,10 @@ (c-basic-offset . 8) (tab-width . 8) )) -) + + (c++-mode . ((c-file-style . "linux") + (indent-tabs-mode . t) + (show-trailing-whitespace . t) + (c-basic-offset . 8) + (tab-width . 8) + ))) diff --git a/.github/scripts/build.sh b/.github/scripts/build.sh index 1037cc97d8aa..0771908b3341 100755 --- a/.github/scripts/build.sh +++ b/.github/scripts/build.sh @@ -31,6 +31,8 @@ poetry install git clone https://github.com/lightning/bolts.git ../${BOLTDIR} git submodule update --init --recursive +wget -q --directory-prefix=contrib/remote_hsmd https://gitlab.com/lightning-signer/validating-lightning-signer/-/raw/master/src/server/remotesigner.proto + ./configure CC="$CC" cat config.vars diff --git a/Makefile b/Makefile index a5da85b84d87..285f43a02f52 100644 --- a/Makefile +++ b/Makefile @@ -242,7 +242,8 @@ LIBRARY_PATH := /usr/local/lib endif CPPFLAGS += -DBINTOPKGLIBEXECDIR="\"$(shell sh tools/rel.sh $(bindir) $(pkglibexecdir))\"" -CFLAGS = $(CPPFLAGS) $(CWARNFLAGS) $(CDEBUGFLAGS) $(COPTFLAGS) -I $(CCANDIR) $(EXTERNAL_INCLUDE_FLAGS) -I . -I$(CPATH) $(SQLITE3_CFLAGS) $(POSTGRES_INCLUDE) $(FEATURES) $(COVFLAGS) $(DEV_CFLAGS) -DSHACHAIN_BITS=48 -DJSMN_PARENT_LINKS $(PIE_CFLAGS) $(COMPAT_CFLAGS) $(CSANFLAGS) -DBUILD_ELEMENTS=1 +CMNFLAGS = $(CDEBUGFLAGS) $(COPTFLAGS) -I $(CCANDIR) $(EXTERNAL_INCLUDE_FLAGS) -I . -I$(CPATH) $(SQLITE3_CFLAGS) $(POSTGRES_INCLUDE) $(FEATURES) $(COVFLAGS) $(DEV_CFLAGS) -DSHACHAIN_BITS=48 -DJSMN_PARENT_LINKS $(PIE_CFLAGS) $(COMPAT_CFLAGS) $(CSANFLAGS) -DBUILD_ELEMENTS=1 +CFLAGS = $(CPPFLAGS) $(CWARNFLAGS) $(CMNFLAGS) # If CFLAGS is already set in the environment of make (to whatever value, it # does not matter) then it would export it to subprocesses with the above value @@ -359,6 +360,7 @@ include devtools/Makefile include tools/Makefile include plugins/Makefile include tests/plugins/Makefile +include contrib/remote_hsmd/Makefile ifneq ($(FUZZING),0) include tests/fuzz/Makefile @@ -407,7 +409,8 @@ PKGLIBEXEC_PROGRAMS = \ lightningd/lightning_hsmd \ lightningd/lightning_onchaind \ lightningd/lightning_openingd \ - lightningd/lightning_websocketd + lightningd/lightning_websocketd \ + lightningd/remote_hsmd mkdocs.yml: $(MANPAGES:=.md) @$(call VERBOSE, "genidx $@", \ diff --git a/contrib/remote_hsmd/.gitignore b/contrib/remote_hsmd/.gitignore new file mode 100644 index 000000000000..48222a90f3d6 --- /dev/null +++ b/contrib/remote_hsmd/.gitignore @@ -0,0 +1,5 @@ +/remotesigner.grpc.pb.cc +/remotesigner.grpc.pb.h +/remotesigner.pb.cc +/remotesigner.pb.h +/remotesigner.proto diff --git a/contrib/remote_hsmd/Makefile b/contrib/remote_hsmd/Makefile new file mode 100644 index 000000000000..f7ef5483db3a --- /dev/null +++ b/contrib/remote_hsmd/Makefile @@ -0,0 +1,124 @@ +#! /usr/bin/make + +# Designed to be run at the top +rmthsmd-wrongdir: + $(MAKE) -C ../.. lightningd/hsm-all + +default: rmthsmd-all + +LIGHTNINGD_RMTHSM_SRC := contrib/remote_hsmd/hsmd.c \ + contrib/remote_hsmd/remotesigner.pb.cc \ + contrib/remote_hsmd/remotesigner.grpc.pb.cc \ + contrib/remote_hsmd/dump.cc \ + contrib/remote_hsmd/proxy.cc +LIGHTNINGD_RMTHSM_HEADERS := contrib/remote_hsmd/remotesigner.pb.h \ + contrib/remote_hsmd/remotesigner.grpc.pb.h +LIGHTNINGD_RMTHSM_CCOBJS := $(LIGHTNINGD_RMTHSM_SRC:.cc=.o) +LIGHTNINGD_RMTHSM_OBJS := $(LIGHTNINGD_RMTHSM_CCOBJS:.c=.o) + +HOST_SYSTEM = $(shell uname | cut -f 1 -d_) +SYSTEM ?= $(HOST_SYSTEM) +CXX = g++ +CXXFLAGS += -std=c++11 \ + -I. \ + -Iccan \ + -Iexternal/libwally-core/include \ + -Iexternal/libwally-core/src/secp256k1/include \ + -g +PROTOC = protoc +GRPC_CPP_PLUGIN = grpc_cpp_plugin +GRPC_CPP_PLUGIN_PATH ?= `which $(GRPC_CPP_PLUGIN)` +PROTOS_PATH = contrib/remote_hsmd + +# Common source we use. +RMTHSMD_COMMON_OBJS := \ + hsmd/hsmd_wiregen.o \ + common/amount.o \ + common/autodata.o \ + common/base32.o \ + common/bigsize.o \ + common/bip32.o \ + common/channel_id.o \ + common/channel_type.o \ + common/daemon.o \ + common/daemon_conn.o \ + common/derive_basepoints.o \ + common/features.o \ + common/status_wire.o \ + common/status_wiregen.o \ + common/hash_u5.o \ + common/htlc_state.o \ + common/htlc_wire.o \ + common/key_derive.o \ + common/memleak.o \ + common/msg_queue.o \ + common/node_id.o \ + common/onionreply.o \ + common/permute_tx.o \ + common/pseudorand.o \ + common/setup.o \ + common/status.o \ + common/status_wire.o \ + common/status_wiregen.o \ + common/subdaemon.o \ + common/type_to_string.o \ + common/utils.o \ + common/utxo.o \ + common/version.o \ + common/wireaddr.o + +# For checking +LIGHTNINGD_RMTHSM_ALLSRC_NOGEN := $(filter-out contrib/remote_hsmd/remotesigner.%, $(LIGHTNINGD_RMTHSM_SRC) $(LIGHTNINGD_RMTHSM_SRC)) +LIGHTNINGD_RMTHSM_ALLHEADERS_NOGEN := $(filter-out contrib/remote_hsmd/remotesigner.%, $(LIGHTNINGD_RMTHSM_HEADERS)) + +$(LIGHTNINGD_RMTHSM_OBJS): $(LIGHTNINGD_HEADERS) + +# Make sure these depend on everything. +ALL_OBJS += $(LIGHTNINGD_RMTHSM_OBJS) +ALL_PROGRAMS += lightningd/remote_hsmd +ALL_GEN_HEADERS += contrib/remote_hsmd/remotesigner.pb.h contrib/remote_hsmd/remotesigner.grpc.pb.h + +rmthsmd-all: lightningd/remote_hsmd + +lightningd/remote_hsmd: $(LIGHTNINGD_RMTHSM_OBJS) $(LIGHTNINGD_LIB_OBJS) $(RMTHSMD_COMMON_OBJS) $(BITCOIN_OBJS) $(WIRE_OBJS) + +contrib/remote_hsmd/remotesigner.pb.o: $(ALL_GEN_HEADERS) + +contrib/remote_hsmd/dump.o contrib/remote_hsmd/proxy.o: $(ALL_GEN_HEADERS) + +contrib/remote_hsmd/remotesigner.grpc.pb.o contrib/remote_hsmd/remotesigner.pb.o contrib/remote_hsmd/proxy.o contrib/remote_hsmd/dump.o : CPPFLAGS += $(CMNFLAGS) `pkg-config --cflags protobuf grpc` +lightningd/remote_hsmd: LDLIBS += -L/usr/local/lib \ + `pkg-config --libs protobuf grpc++`\ + -pthread\ + -Wl,--no-as-needed -lgrpc++_reflection -Wl,--as-needed\ + -ldl +lightningd/remote_hsmd: LDLIBS += -lstdc++ + +check-source: $(LIGHTNINGD_RMTHSM_ALLSRC_NOGEN:%=check-src-include-order/%) $(LIGHTNINGD_RMTHSM_ALLHEADERS_NOGEN:%=check-hdr-include-order/%) +check-source-bolt: $(LIGHTNINGD_RMTHSM_SRC:%=bolt-check/%) + +check-whitespace: $(LIGHTNINGD_RMTHSM_ALLSRC_NOGEN:%=check-whitespace/%) $(LIGHTNINGD_RMTHSM_ALLHEADERS_NOGEN:%=check-whitespace/%) + +clean: lightningd/rmthsm-clean + +lightningd/rmthsm-clean: + $(RM) $(LIGHTNINGD_RMTHSM_OBJS) + $(RM) contrib/remote_hsmd/*.pb.cc contrib/remote_hsmd/*.pb.h + +.PRECIOUS: %.grpc.pb.cc +%.grpc.pb.cc: %.proto + $(PROTOC) -I $(PROTOS_PATH) --grpc_out=contrib/remote_hsmd --plugin=protoc-gen-grpc=$(GRPC_CPP_PLUGIN_PATH) $< + +.PRECIOUS: %.pb.cc +%.pb.cc: %.proto + $(PROTOC) -I $(PROTOS_PATH) --cpp_out=contrib/remote_hsmd $< + +.PRECIOUS: %.grpc.pb.h +%.grpc.pb.h: %.proto + $(PROTOC) -I $(PROTOS_PATH) --grpc_out=contrib/remote_hsmd --plugin=protoc-gen-grpc=$(GRPC_CPP_PLUGIN_PATH) $< + +.PRECIOUS: %.pb.h +%.pb.h: %.proto + $(PROTOC) -I $(PROTOS_PATH) --cpp_out=contrib/remote_hsmd $< + +-include contrib/remote_hsmd/test/Makefile diff --git a/contrib/remote_hsmd/NOTES.md b/contrib/remote_hsmd/NOTES.md new file mode 100644 index 000000000000..6c47bf8e530f --- /dev/null +++ b/contrib/remote_hsmd/NOTES.md @@ -0,0 +1,113 @@ +c-lightning +---------------------------------------------------------------- + +Setup + + # Add an "upstream" reference and fetch all tags (needed for pyln-{proto,client,testing}) + git remote add upstream git@github.com:ElementsProject/lightning.git + git fetch upstream --tags + + (cd contrib/remote_hsmd && \ + ln -s ../../../validating-lightning-signer/lightning-signer-server/src/server/remotesigner.proto) + +Additional Dependencies (needed after applying steps in `doc/INSTALL`): + +On Ubuntu: + + sudo apt-get install -y libgrpc-dev libgrpc++-dev protobuf-compiler-grpc + +On Fedora: + + sudo dnf install -y grpc-devel grpc-plugins + +Building + + make distclean + ./configure --enable-developer + make + +Build libsecp256k1 with `./configure --enable-module-recovery`, see +https://github.com/golemfactory/golem/issues/2168 for background. + + pip3 install --user base58 + pip3 install --user bitstring + pip3 install --user secp256k1 + pip3 install --user mrkd + + # in c-lightning root: + pip3 install --user -r requirements.txt + +Run all of the integration tests: + + ./contrib/remote_hsmd/scripts/run-all-tests |& tee log + +Run a single test: + + ./contrib/remote_hsmd/scripts/run-one-test $THETEST |& tee log + +Re-run failures from prior run: + + ./contrib/remote_hsmd/scripts/rerun-failed-tests < log |& tee log2 + +To run tests using anchors, rebuild w/ experimental features: + + make distclean + ./configure --enable-developer --enable-experimental-features + make + + ./contrib/remote_hsmd/scripts/run-all-tests |& tee log + +Some popular tests: + + # sign-invoice, handle-sign-remote-htlc-tx + export THETEST=tests/test_connection.py::test_balance + export THETEST=tests/test_pay.py::test_sendpay + export THETEST=tests/test_pay.py::test_pay + + # sign-local-htlc-tx + export THETEST=tests/test_closing.py::test_onchain_different_fees + + # sign-remote-htlc-to-us + export THETEST=tests/test_closing.py::test_onchain_feechange + export THETEST=tests/test_closing.py::test_onchain_all_dust + export THETEST=tests/test_closing.py::test_permfail_new_commit + + # sign-delayed-payment-to-us + export THETEST=tests/test_closing.py::test_onchain_multihtlc_our_unilateral + export THETEST=tests/test_closing.py::test_onchain_multihtlc_their_unilateral + export THETEST=tests/test_closing.py::test_permfail_htlc_in + export THETEST=tests/test_closing.py::test_permfail_htlc_out + + # sign-penalty-to-us + export THETEST=tests/test_closing.py::test_penalty_inhtlc + export THETEST=tests/test_closing.py::test_penalty_outhtlc + export THETEST=tests/test_closing.py::test_closing + + # sign-mutual-close + export THETEST=tests/test_closing.py::test_closing + + # check-future-secret + export THETEST=tests/test_connection.py::test_dataloss_protection + + # sign-message + export THETEST=tests/test_misc.py::test_signmessage + + # sign-channel-announcement + export THETEST=tests/test_closing.py::test_closing_different_fees + + # P2SH_P2WPKH + export THETEST=tests/test_closing.py::test_onchain_first_commit + export THETEST=tests/test_connection.py::test_disconnect_funder + export THETEST=tests/test_connection.py::test_disconnect_fundee + export THETEST=tests/test_connection.py::test_reconnect_signed + export THETEST=tests/test_connection.py::test_reconnect_openingd + export THETEST=tests/test_connection.py::test_shutdown_awaiting_lockin + + # unilateral_close_info option_static_remotekey + export THETEST=tests/test_connection.py::test_fee_limits + export THETEST=tests/test_closing.py::test_option_upfront_shutdown_script + +validating-lightning-signer +---------------------------------------------------------------- + + cargo run --bin server -- --no-persist --test-mode |& tee log3 diff --git a/contrib/remote_hsmd/TODO.md b/contrib/remote_hsmd/TODO.md new file mode 100644 index 000000000000..1b22735f129b --- /dev/null +++ b/contrib/remote_hsmd/TODO.md @@ -0,0 +1,44 @@ + +API Coverage +---------------------------------------------------------------- + +## Failing Tests + +# Frequently Intermittent +tests/test_connection.py::test_funding_cancel_race +tests/test_misc.py::test_bad_onion_immediate_peer + +## Proxy Scoreboard + +``` +COMPLETE proxy_stat proxy_handle_ecdh +COMPLETE proxy_stat proxy_handle_pass_client_hsmfd +COMPLETE proxy_stat proxy_handle_sign_remote_commitment_tx +COMPLETE proxy_stat proxy_handle_channel_update_sig +COMPLETE proxy_stat proxy_handle_sign_node_announcement +COMPLETE proxy_stat proxy_handle_sign_remote_htlc_tx +COMPLETE proxy_stat proxy_handle_sign_invoice +COMPLETE proxy_stat proxy_handle_get_channel_basepoints +COMPLETE proxy_stat proxy_handle_get_per_commitment_point +COMPLETE proxy_stat proxy_handle_sign_local_htlc_tx +COMPLETE proxy_stat proxy_handle_sign_remote_htlc_to_us +COMPLETE proxy_stat proxy_handle_sign_delayed_payment_to_us +COMPLETE proxy_stat proxy_handle_sign_penalty_to_us +COMPLETE proxy_stat proxy_handle_sign_commitment_tx +COMPLETE proxy_stat proxy_handle_sign_mutual_close_tx +COMPLETE proxy_stat proxy_handle_check_future_secret +COMPLETE proxy_stat proxy_handle_cannouncement_sig +COMPLETE proxy_stat proxy_handle_sign_message + +PARTIAL (-P2SH) proxy_stat proxy_handle_sign_withdrawal_tx + +MARSHALED proxy_stat proxy_init_hsm +``` + +Improvements +---------------------------------------------------------------- + +#### Remove contrib/remote_signer/hsm_wire.csv + +Generate gen_hsm_wire.{h,c} from c-lightning/hsmd/hsm_wire.csv instead. + diff --git a/contrib/remote_hsmd/capabilities.h b/contrib/remote_hsmd/capabilities.h new file mode 100644 index 000000000000..3c139c26de70 --- /dev/null +++ b/contrib/remote_hsmd/capabilities.h @@ -0,0 +1,12 @@ +#ifndef LIGHTNING_CONTRIB_REMOTE_HSMD_CAPABILITIES_H +#define LIGHTNING_CONTRIB_REMOTE_HSMD_CAPABILITIES_H + +#define HSM_CAP_ECDH 1 +#define HSM_CAP_SIGN_GOSSIP 2 +#define HSM_CAP_SIGN_ONCHAIN_TX 4 +#define HSM_CAP_COMMITMENT_POINT 8 +#define HSM_CAP_SIGN_REMOTE_TX 16 +#define HSM_CAP_SIGN_CLOSING_TX 32 + +#define HSM_CAP_MASTER 1024 +#endif /* LIGHTNING_CONTRIB_REMOTE_HSMD_CAPABILITIES_H */ diff --git a/contrib/remote_hsmd/dump.cc b/contrib/remote_hsmd/dump.cc new file mode 100644 index 000000000000..2e29b10b90e3 --- /dev/null +++ b/contrib/remote_hsmd/dump.cc @@ -0,0 +1,548 @@ +#include "contrib/remote_hsmd/dump.hpp" + +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +} +#include +#include +extern "C" { +#include +#include +} + + +using std::ostringstream; +using std::string; + +bool pubkey_is_compressed(const unsigned char pubkey[EC_PUBLIC_KEY_UNCOMPRESSED_LEN]) { + return pubkey[0] == 0x02 || pubkey[0] == 0x03; +} + +/* dump an optional wallet index */ +string dump_optional_wallet_index(u32 *optional_wallet_index) +{ + ostringstream ostrm; + if (optional_wallet_index == NULL) { + ostrm << "NONE"; + } else { + ostrm << *optional_wallet_index; + } + return ostrm.str(); +} + +/* type_to_string has issues in the C++ environment, use this to + dump binary data as hex instead. */ +string dump_hex(const void *vptr, size_t sz) +{ + static const char hex[] = "0123456789abcdef"; + ostringstream ostrm; + ostrm << '"'; + uint8_t const * ptr = (uint8_t const *) vptr; + for (size_t ii = 0; ii < sz; ++ii) { + ostrm << hex[(*ptr) >> 4] + << hex[(*ptr) & 0xf]; + ++ptr; + } + ostrm << '"'; + return ostrm.str(); +} + +string dump_bitcoin_txid(const struct bitcoin_txid *txid) +{ + // reverse the bytes, a-la bitcoind + struct sha256_double rev = txid->shad; + reverse_bytes(rev.sha.u.u8, sizeof(rev.sha.u.u8)); + return dump_hex(rev.sha.u.u8, sizeof(rev.sha.u.u8)); +} + +string dump_bitcoin_signature(const struct bitcoin_signature *sp) +{ + ostringstream ostrm; + ostrm << "{ " + << "\"sighash_type\":" << int(sp->sighash_type) + << ", \"s\":" + << dump_secp256k1_ecdsa_signature(&sp->s) + << " }"; + return ostrm.str(); +} + +string dump_htlc_signatures(const struct bitcoin_signature *sps) +{ + ostringstream ostrm; + ostrm << "["; + for (size_t input_ndx = 0; input_ndx < tal_count(sps); ++input_ndx) { + if (input_ndx != 0) + ostrm << ", "; + ostrm << dump_bitcoin_signature(&sps[input_ndx]); + } + ostrm << "]"; + return ostrm.str(); +} + +string dump_secp256k1_ecdsa_signature(const secp256k1_ecdsa_signature *sp) +{ + return dump_hex(sp->data, sizeof(sp->data)); +} + +string dump_secp256k1_ecdsa_recoverable_signature( + const secp256k1_ecdsa_recoverable_signature *sp) +{ + return dump_hex(sp->data, sizeof(sp->data)); +} + +string dump_secret(const struct secret *sp) +{ + return dump_hex(sp->data, sizeof(sp->data)); +} + +string dump_node_id(const struct node_id *pp) +{ + return dump_hex(pp->k, sizeof(pp->k)); +} + +string dump_pubkey(const struct pubkey *kp) +{ + return dump_hex(kp->pubkey.data, sizeof(kp->pubkey.data)); +} + +string dump_ext_pubkey(const struct ext_key *xp) +{ + char *out; + tal_wally_start(); + int rv = bip32_key_to_base58(xp, BIP32_FLAG_KEY_PUBLIC, &out); + tal_wally_end(NULL); + assert(rv == WALLY_OK); + string retval(out); + wally_free_string(out); + return retval; +} + +string dump_witnesses(const u8 ***wp) +{ + ostringstream ostrm; + ostrm << "["; + for (size_t input_ndx = 0; input_ndx < tal_count(wp); ++input_ndx) { + if (input_ndx != 0) + ostrm << ", "; + ostrm << "["; + u8 const *sig = wp[input_ndx][0]; + ostrm << dump_hex(sig, tal_count(sig)); + ostrm << ", "; + u8 const *pubkey = wp[input_ndx][1]; + ostrm << dump_hex(pubkey, tal_count(pubkey)); + ostrm << "]"; + } + ostrm << "]"; + return ostrm.str(); +} + +string dump_basepoints(const struct basepoints *bp) +{ + ostringstream ostrm; + ostrm << "{ "; + ostrm << "\"revocation\":" << dump_pubkey(&bp->revocation); + ostrm << ", \"payment\":" << dump_pubkey(&bp->payment); + ostrm << ", \"htlc\":" << dump_pubkey(&bp->htlc); + ostrm << ", \"delayed_payment\":" << dump_pubkey(&bp->delayed_payment); + ostrm << " }"; + return ostrm.str(); +} + +string dump_unilateral_close_info(const struct unilateral_close_info *ip) +{ + ostringstream ostrm; + ostrm << "{ "; + ostrm << "\"channel_id\":" << ip->channel_id; + ostrm << ", \"peer_id\":" << dump_node_id(&ip->peer_id); + ostrm << ", \"commitment_point\":" << + (ip->commitment_point ? dump_pubkey(ip->commitment_point) : + "\"\""); + ostrm << " }"; + return ostrm.str(); +} + +string dump_utxo(const struct utxo *in) +{ + ostringstream ostrm; + ostrm << "{ "; + ostrm << "\"txid\":" << dump_bitcoin_txid(&in->outpoint.txid); + ostrm << ", \"outnum\":" << in->outpoint.n; + ostrm << ", \"amount\":" << in->amount.satoshis; + ostrm << ", \"keyindex\":" << in->keyindex; + ostrm << ", \"is_p2sh\":" << in->is_p2sh; + ostrm << ", \"close_info\":" << + (in->close_info ? + dump_unilateral_close_info(in->close_info) : + "\"\""); + ostrm << " }"; + return ostrm.str(); +} + +string dump_utxos(const struct utxo **utxos) +{ + ostringstream ostrm; + ostrm << "["; + for (size_t ii = 0; ii < tal_count(utxos); ii++) { + if (ii != 0) + ostrm << ","; + ostrm << dump_utxo(utxos[ii]); + } + ostrm << "]"; + return ostrm.str(); +} + +string dump_bitcoin_tx_output(const struct bitcoin_tx_output *op) +{ + ostringstream ostrm; + ostrm << "{ "; + ostrm << "\"amount\":" << op->amount.satoshis; + ostrm << ", \"script\":" << + (op->script ? dump_hex(op->script, tal_count(op->script)) : "\"\""); + ostrm << " }"; + return ostrm.str(); +} + +string dump_bitcoin_tx_outputs(const struct bitcoin_tx_output **outputs) +{ + ostringstream ostrm; + ostrm << "["; + for (size_t ii = 0; ii < tal_count(outputs); ii++) { + if (ii != 0) + ostrm << ","; + ostrm << dump_bitcoin_tx_output(outputs[ii]); + } + ostrm << "]"; + return ostrm.str(); +} + +string dump_wally_tx_witness_stack(const struct wally_tx_witness_stack *sp) +{ + if (sp == NULL) + return "[]"; + ostringstream ostrm; + ostrm << "["; + for (size_t ii = 0; ii < sp->num_items; ii++) { + if (ii != 0) + ostrm << ","; + ostrm << dump_hex(sp->items[ii].witness, + sp->items[ii].witness_len); + } + ostrm << "]"; + return ostrm.str(); +} + +string dump_wally_keypath_item(const struct wally_map_item *ip) +{ + size_t npath = (ip->value_len - BIP32_KEY_FINGERPRINT_LEN) / sizeof(uint32_t); + uint32_t * path = (uint32_t *) (ip->value + BIP32_KEY_FINGERPRINT_LEN); + ostringstream ostrm; + ostrm << "{ "; + ostrm << "\"pubkey\":" << dump_hex(ip->key, ip->key_len); + ostrm << ", \"origin\":{ "; + ostrm << " \"fingerprint\":" + << dump_hex(ip->value, BIP32_KEY_FINGERPRINT_LEN); + ostrm << ", \"path\":[ "; + for (size_t ii = 0; ii < npath; ++ii) { + if (ii != 0) + ostrm << ","; + ostrm << le32_to_cpu(path[ii]); + } + ostrm << " ]"; + ostrm << " }"; + ostrm << " }"; + return ostrm.str(); +} + +string dump_wally_keypath_map(const struct wally_map *mp) +{ + ostringstream ostrm; + ostrm << "["; + if (mp) { + for (size_t ii = 0; ii < mp->num_items; ii++) { + if (ii != 0) + ostrm << ","; + ostrm << dump_wally_keypath_item(&mp->items[ii]); + } + } + ostrm << "]"; + return ostrm.str(); +} + +string dump_wally_signatures_item(const struct wally_map_item *ip) +{ + ostringstream ostrm; + ostrm << "{ "; + ostrm << "\"pubkey\":" << dump_hex(ip->key, ip->key_len); + ostrm << ", \"sig\":" << dump_hex(ip->value, ip->value_len); + ostrm << " }"; + return ostrm.str(); +} + +string dump_wally_signatures_map(const struct wally_map *mp) +{ + ostringstream ostrm; + ostrm << "["; + if (mp) { + for (size_t ii = 0; ii < mp->num_items; ii++) { + if (ii != 0) + ostrm << ","; + ostrm << dump_wally_signatures_item(&mp->items[ii]); + } + } + ostrm << "]"; + return ostrm.str(); +} + +string dump_wally_unknowns_item(const struct wally_map_item *ip) +{ + ostringstream ostrm; + ostrm << "{ "; + ostrm << "\"key\":" << dump_hex(ip->key, ip->key_len); + ostrm << ", \"value\":" << dump_hex(ip->value, ip->value_len); + ostrm << " }"; + return ostrm.str(); +} + +string dump_wally_unknowns_map(const struct wally_map *mp) +{ + ostringstream ostrm; + ostrm << "["; + if (mp) { + for (size_t ii = 0; ii < mp->num_items; ii++) { + if (ii != 0) + ostrm << ","; + ostrm << dump_wally_unknowns_item(&mp->items[ii]); + } + } + ostrm << "]"; + return ostrm.str(); +} + +string dump_wally_tx_input(const struct wally_tx_input *in) +{ + ostringstream ostrm; + ostrm << "{ "; + ostrm << "\"txhash\":" << dump_hex(in->txhash, sizeof(in->txhash)); + ostrm << ", \"index\":" << in->index; + ostrm << ", \"sequence\":" << in->sequence; + ostrm << ", \"script\":" << + (in->script_len ? dump_hex(in->script, in->script_len) : + "\"\""); + ostrm << ", \"witness\":" << + (in->witness ? dump_wally_tx_witness_stack(in->witness) : + "\"\""); + ostrm << ", \"features\":" << int(in->features); + ostrm << " }"; + return ostrm.str(); +} + +string dump_wally_tx_inputs(const struct wally_tx_input *inputs, + size_t num_inputs) +{ + ostringstream ostrm; + ostrm << "["; + for (size_t ii = 0; ii < num_inputs; ii++) { + if (ii != 0) + ostrm << ","; + ostrm << dump_wally_tx_input(&inputs[ii]); + } + ostrm << "]"; + return ostrm.str(); +} + +string dump_wally_tx_output(const struct wally_tx_output *out) +{ + if (out == NULL) + return "{}"; + ostringstream ostrm; + ostrm << "{ "; + ostrm << "\"satoshi\":" << out->satoshi; + ostrm << ", \"script\":" << + (out->script_len ? dump_hex(out->script, out->script_len) : + "\"\""); + ostrm << ", \"features\":" << int(out->features); + ostrm << " }"; + return ostrm.str(); +} + +string dump_wally_tx_outputs(const struct wally_tx_output *outputs, + size_t num_outputs) +{ + ostringstream ostrm; + ostrm << "["; + for (size_t ii = 0; ii < num_outputs; ii++) { + if (ii != 0) + ostrm << ","; + ostrm << dump_wally_tx_output(&outputs[ii]); + } + ostrm << "]"; + return ostrm.str(); +} + +string dump_wally_tx(const struct wally_tx *wtx) +{ + if (wtx == NULL) + return "{}"; + ostringstream ostrm; + ostrm << "{ "; + ostrm << "\"version\":" << wtx->version; + ostrm << ", \"locktime\":" << wtx->locktime; + ostrm << ", \"inputs\":" << + dump_wally_tx_inputs(wtx->inputs, wtx->num_inputs); + ostrm << ", \"inputs_allocation_len\":" << wtx->inputs_allocation_len; + ostrm << ", \"outputs\":" << + dump_wally_tx_outputs(wtx->outputs, wtx->num_outputs); + ostrm << ", \"outputs_allocation_len\":" << wtx->outputs_allocation_len; + ostrm << " }"; + return ostrm.str(); +} + +string dump_wally_psbt_input(const struct wally_psbt_input *in) +{ + ostringstream ostrm; + ostrm << "{ "; + ostrm << "\"utxo\":" << dump_wally_tx(in->utxo); + ostrm << ", \"witness_utxo\":" << dump_wally_tx_output(in->witness_utxo); + ostrm << ", \"redeem_script\":" << dump_hex(in->redeem_script, + in->redeem_script_len); + ostrm << ", \"witness_script\":" << dump_hex(in->witness_script, + in->witness_script_len); + ostrm << ", \"final_scriptsig\":" << dump_hex(in->final_scriptsig, + in->final_scriptsig_len); + ostrm << ", \"final_witness\":" + << dump_wally_tx_witness_stack(in->final_witness); + ostrm << ", \"keypaths\":" << dump_wally_keypath_map(&in->keypaths); + ostrm << ", \"signatures\":" + << dump_wally_signatures_map(&in->signatures); + ostrm << ", \"unknowns\":" << dump_wally_unknowns_map(&in->unknowns); + ostrm << ", \"sighash\":" << in->sighash; + ostrm << " }"; + return ostrm.str(); +} + +string dump_wally_psbt_inputs(const struct wally_psbt_input *inputs, + size_t num_inputs) +{ + ostringstream ostrm; + ostrm << "["; + for (size_t ii = 0; ii < num_inputs; ii++) { + if (ii != 0) + ostrm << ","; + ostrm << dump_wally_psbt_input(&inputs[ii]); + } + ostrm << "]"; + return ostrm.str(); +} + +string dump_wally_psbt_output(const struct wally_psbt_output *out) +{ + ostringstream ostrm; + ostrm << "{ "; + ostrm << "\"redeem_script\":" << dump_hex(out->redeem_script, + out->redeem_script_len); + ostrm << ", \"witness_script\":" << dump_hex(out->witness_script, + out->witness_script_len); + ostrm << ", \"keypaths\":" << dump_wally_keypath_map(&out->keypaths); + ostrm << ", \"unknowns\":" << dump_wally_unknowns_map(&out->unknowns); + ostrm << " }"; + return ostrm.str(); +} + +string dump_wally_psbt_outputs(const struct wally_psbt_output *outputs, + size_t num_outputs) +{ + ostringstream ostrm; + ostrm << "["; + for (size_t ii = 0; ii < num_outputs; ii++) { + if (ii != 0) + ostrm << ","; + ostrm << dump_wally_psbt_output(&outputs[ii]); + } + ostrm << "]"; + return ostrm.str(); +} + +string dump_wally_psbt(const struct wally_psbt *psbt) +{ + ostringstream ostrm; + ostrm << "{ "; + ostrm << "\"magic\":" << dump_hex(psbt->magic, sizeof(psbt->magic)); + ostrm << ", \"tx\":" << dump_wally_tx(psbt->tx); + ostrm << ", \"inputs\":" + << dump_wally_psbt_inputs(psbt->inputs, psbt->num_inputs); + ostrm << ", \"outputs\":" + << dump_wally_psbt_outputs(psbt->outputs, psbt->num_outputs); + ostrm << ", \"unknowns\":" << dump_wally_unknowns_map(&psbt->unknowns); + ostrm << ", \"version\":" << psbt->version; + ostrm << " }"; + return ostrm.str(); +} + +string dump_tx(const struct bitcoin_tx *tx) +{ + ostringstream ostrm; + ostrm << "{ "; + ostrm << "\"wtx\":" << dump_wally_tx(tx->wtx); + ostrm << ", \"psbt\":" << dump_wally_psbt(tx->psbt); + ostrm << " }"; + return ostrm.str(); +} + +string dump_rhashes(const struct sha256 *rhashes, size_t num_rhashes) +{ + ostringstream ostrm; + ostrm << "["; + for (size_t ii = 0; ii < num_rhashes; ii++) { + if (ii != 0) + ostrm << ","; + ostrm << dump_hex(&rhashes[ii], sizeof(rhashes[ii])); + } + ostrm << "]"; + return ostrm.str(); +} + +string dump_htlc(const struct simple_htlc *htlc) +{ + ostringstream ostrm; + ostrm << "{ " + << ", \"side\":" << htlc->side + << ", \"amount_msat\":" << htlc->amount.millisatoshis + << ", \"payment_hash\":" << dump_hex(&htlc->payment_hash, sizeof(htlc->payment_hash)) + << ", \"cltv_expiry\":" << htlc->cltv_expiry + << " }"; + return ostrm.str(); +} + +string dump_htlcs(const struct simple_htlc **htlc, size_t num_htlc) +{ + ostringstream ostrm; + ostrm << "["; + for (size_t ii = 0; ii < num_htlc; ii++) { + if (ii != 0) + ostrm << ","; + ostrm << dump_htlc(htlc[ii]); + } + ostrm << "]"; + return ostrm.str(); +} + +/* . Bitcoind represents hashes as little-endian for RPC. */ +void reverse_bytes(u8 *arr, size_t len) +{ + unsigned int i; + + for (i = 0; i < len / 2; i++) { + unsigned char tmp = arr[i]; + arr[i] = arr[len - 1 - i]; + arr[len - 1 - i] = tmp; + } +} diff --git a/contrib/remote_hsmd/dump.hpp b/contrib/remote_hsmd/dump.hpp new file mode 100644 index 000000000000..3172847a126d --- /dev/null +++ b/contrib/remote_hsmd/dump.hpp @@ -0,0 +1,48 @@ +#ifndef LIGHTNING_CONTRIB_REMOTE_HSMD_DUMP_H +#define LIGHTNING_CONTRIB_REMOTE_HSMD_DUMP_H + +extern "C" { +#include +#include +} +#include + +std::string dump_optional_wallet_index(u32 *optional_wallet_index); +std::string dump_hex(const void *vptr, size_t sz); +std::string dump_basepoints(const struct basepoints *bp); +std::string dump_bitcoin_txid(const struct bitcoin_txid *txid); +std::string dump_bitcoin_signature(const struct bitcoin_signature *sp); +std::string dump_htlc_signatures(const struct bitcoin_signature *sps); +std::string dump_secp256k1_ecdsa_signature(const secp256k1_ecdsa_signature *sp); +std::string dump_secp256k1_ecdsa_recoverable_signature(const secp256k1_ecdsa_recoverable_signature *sp); +std::string dump_secret(const struct secret *sp); +std::string dump_node_id(const struct node_id *pp); +std::string dump_pubkey(const struct pubkey *kp); +std::string dump_ext_pubkey(const struct ext_key *xp); +std::string dump_witnesses(const u8 ***wp); +std::string dump_unilateral_close_info(const struct unilateral_close_info *ip); +std::string dump_utxo(const struct utxo *in); +std::string dump_utxos(const struct utxo **utxos); +std::string dump_bitcoin_tx_output(const struct bitcoin_tx_output *op); +std::string dump_bitcoin_tx_outputs(const struct bitcoin_tx_output **outputs); +std::string dump_wally_tx_witness_stack(const struct wally_tx_witness_stack *sp); +std::string dump_wally_keypath_map(const struct wally_map *mp); +std::string dump_wally_partial_sigs_map(const struct wally_map *mp); +std::string dump_wally_unknowns_map(const struct wally_map *mp); +std::string dump_wally_tx_input(const struct wally_tx_input *in); +std::string dump_wally_tx_inputs(const struct wally_tx_input *inputs, + size_t num_inputs); +std::string dump_wally_tx_output(const struct wally_tx_output *out); +std::string dump_wally_tx_outputs(const struct wally_tx_output *outputs, + size_t num_outputs); +std::string dump_wally_tx(const struct wally_tx *wtx); +std::string dump_wally_psbt(const struct wally_psbt *psbt); +std::string dump_tx(const struct bitcoin_tx *tx); +std::string dump_rhashes(const struct sha256 *rhashes, size_t num_rhashes); +std::string dump_htlc(const struct simple_htlc *htlc); +std::string dump_htlcs(const struct simple_htlc **htlc, size_t num_htlc); + +// needed for formatting txid +void reverse_bytes(u8 *arr, size_t len); + +#endif /* LIGHTNING_CONTRIB_REMOTE_HSMD_DUMP_H */ diff --git a/contrib/remote_hsmd/hsmd.c b/contrib/remote_hsmd/hsmd.c new file mode 100644 index 000000000000..c66a5294499c --- /dev/null +++ b/contrib/remote_hsmd/hsmd.c @@ -0,0 +1,1946 @@ +/*~ Welcome to the hsm daemon: keeper of our secrets! + * + * This is a separate daemon which keeps a root secret from which all others + * are generated. It starts with one client: lightningd, which can ask for + * new sockets for other clients. Each client has a simple capability map + * which indicates what it's allowed to ask for. We're entirely driven + * by request, response. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/*~ _wiregen files are autogenerated by tools/generate-wire.py */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/*~ Each subdaemon is started with stdin connected to lightningd (for status + * messages), and stderr untouched (for emergency printing). File descriptors + * 3 and beyond are set up on other sockets: for hsmd, fd 3 is the request + * stream from lightningd. */ +#define REQ_FD 3 + +/* This used to be secretstuff, now has no secrets ... */ +static struct { + struct ext_key bip32; /* Only has public part */ +} pubstuff; + +/* Have we initialized the remote signer? */ +static bool initialized = false; + +/* Version codes for BIP32 extended keys in libwally-core. + * It's not suitable to add this struct into client struct, + * so set it static.*/ +static struct bip32_key_version bip32_key_version; + +#if DEVELOPER +/* If they specify --dev-force-privkey it ends up in here. */ +static struct privkey *dev_force_privkey; +/* If they specify --dev-force-bip32-seed it ends up in here. */ +static struct secret *dev_force_bip32_seed; +#endif + +/* FIXME - REMOVE THIS WHEN NO LONGER NEEDED */ +#if 0 +static void print_hex(char const *tag, void const *vptr, size_t sz) +{ + fprintf(stderr, "%s: ", tag); + uint8_t const *ptr = (uint8_t const *) vptr; + for (size_t ii = 0; ii < sz; ++ii) { + fprintf(stderr, "%02x", (int) ptr[ii]); + } + fprintf(stderr, "\n"); +} +#endif + +/*~ We keep track of clients, but there's not much to keep. */ +struct client { + /* The ccan/io async io connection for this client: it closes, we die. */ + struct io_conn *conn; + + /*~ io_read_wire needs a pointer to store incoming messages until + * it has the complete thing; this is it. */ + u8 *msg_in; + + /*~ Useful for logging, but also used to derive the per-channel seed. */ + struct node_id id; + + /*~ This is a unique value handed to us from lightningd, used for + * per-channel seed generation (a single id may have multiple channels + * over time). + * + * It's actually zero for the initial lightningd client connection and + * the ones for gossipd and connectd, which don't have channels + * associated. */ + u64 dbid; + + /* What is this client allowed to ask for? */ + u64 capabilities; + + /* Params to apply to all transactions for this client */ + const struct chainparams *chainparams; +}; + +/*~ We keep a map of nonzero dbid -> clients, mainly for leak detection. + * This is ccan/uintmap, which maps u64 to some (non-NULL) pointer. + * I really dislike these kinds of declaration-via-magic macro things, as + * tags can't find them without special hacks, but the payoff here is that + * the map is typesafe: the compiler won't let you put anything in but a + * struct client pointer. */ +static UINTMAP(struct client *) clients; +/*~ Plus the three zero-dbid clients: master, gossipd and connnectd. */ +static struct client *dbid_zero_clients[3]; +static size_t num_dbid_zero_clients; + +/*~ We need this deep inside bad_req_fmt, and for memleak, so we make it a + * global. */ +static struct daemon_conn *status_conn; + +/* This is used for various assertions and error cases. */ +static bool is_lightningd(const struct client *client) +{ + return client == dbid_zero_clients[0]; +} + +/* Pre-declare this, due to mutual recursion */ +static struct io_plan *handle_client(struct io_conn *conn, struct client *c); + +/*~ ccan/compiler.h defines PRINTF_FMT as the gcc compiler hint so it will + * check that fmt and other trailing arguments really are the correct type. + * + * This is a convenient helper to tell lightningd we've received a bad request + * and closes the client connection. This should never happen, of course, but + * we definitely want to log if it does. + */ +static struct io_plan *bad_req_fmt(struct io_conn *conn, + struct client *c, + const u8 *msg_in, + const char *fmt, ...) + PRINTF_FMT(4,5); + +static struct io_plan *bad_req_fmt(struct io_conn *conn, + struct client *c, + const u8 *msg_in, + const char *fmt, ...) +{ + va_list ap; + char *str; + + va_start(ap, fmt); + str = tal_fmt(tmpctx, fmt, ap); + va_end(ap); + + /*~ If the client was actually lightningd, it's Game Over; we actually + * fail in this case, and it will too. */ + if (is_lightningd(c)) { + status_broken("%s", str); + master_badmsg(fromwire_peektype(msg_in), msg_in); + } + + /*~ Nobody should give us bad requests; it's a sign something is broken */ + status_broken("%s: %s", type_to_string(tmpctx, struct node_id, &c->id), str); + + /*~ Note the use of NULL as the ctx arg to towire_hsmstatus_: only + * use NULL as the allocation when we're about to immediately free it + * or hand it off with take(), as here. That makes it clear we don't + * expect it to linger, and in fact our memleak detection will + * complain if it does (unlike using the deliberately-transient + * tmpctx). */ + daemon_conn_send(status_conn, + take(towire_hsmstatus_client_bad_request(NULL, + &c->id, + str, + msg_in))); + + /*~ The way ccan/io works is that you return the "plan" for what to do + * next (eg. io_read). io_close() is special: it means to close the + * connection. */ + return io_close(conn); +} + +/* Convenience wrapper for when we simply can't parse. */ +static struct io_plan *bad_req(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + return bad_req_fmt(conn, c, msg_in, "could not parse request"); +} + +/*~ This plan simply says: read the next packet into 'c->msg_in' (parent 'c'), + * and then call handle_client with argument 'c' */ +static struct io_plan *client_read_next(struct io_conn *conn, struct client *c) +{ + c->msg_in = tal_free(c->msg_in); + return io_read_wire(conn, c, &c->msg_in, handle_client, c); +} + +/*~ This is the destructor on our client: we may call it manually, but + * generally it's called because the io_conn associated with the client is + * closed by the other end. */ +static void destroy_client(struct client *c) +{ + if (!uintmap_del(&clients, c->dbid)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Failed to remove client dbid %"PRIu64, c->dbid); +} + +static struct client *new_client(const tal_t *ctx, + const struct chainparams *chainparams, + const struct node_id *id, + u64 dbid, + const u64 capabilities, + int fd) +{ + struct client *c = tal(ctx, struct client); + + c->msg_in = NULL; + + /*~ All-zero pubkey is used for the initial master connection */ + if (id) { + c->id = *id; + if (!node_id_valid(id)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Invalid node id %s", + type_to_string(tmpctx, struct node_id, + id)); + } else { + memset(&c->id, 0, sizeof(c->id)); + } + c->dbid = dbid; + + c->capabilities = capabilities; + c->chainparams = chainparams; + + /*~ This is the core of ccan/io: the connection creation calls a + * callback which returns the initial plan to execute: in our case, + * read a message.*/ + c->conn = io_new_conn(ctx, fd, client_read_next, c); + + /*~ tal_steal() moves a pointer to a new parent. At this point, the + * hierarchy is: + * + * ctx -> c + * ctx -> c->conn + * + * We want to the c->conn to own 'c', so that if the io_conn closes, + * the client is freed: + * + * ctx -> c->conn -> c. + */ + tal_steal(c->conn, c); + + /* We put the special zero-db HSM connections into an array, the rest + * go into the map. */ + if (dbid == 0) { + assert(num_dbid_zero_clients < ARRAY_SIZE(dbid_zero_clients)); + dbid_zero_clients[num_dbid_zero_clients++] = c; + } else { + struct client *old_client = uintmap_get(&clients, dbid); + + /* Close conn and free any old client of this dbid. */ + if (old_client) + io_close(old_client->conn); + + if (!uintmap_add(&clients, dbid, c)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Failed inserting dbid %"PRIu64, dbid); + tal_add_destructor(c, destroy_client); + } + + return c; +} + +/* This is the common pattern for the tail of each handler in this file. */ +static struct io_plan *req_reply(struct io_conn *conn, + struct client *c, + const u8 *msg_out TAKES) +{ + /*~ Write this out, then read the next one. This works perfectly for + * a simple request/response system like this. + * + * Internally, the ccan/io subsystem gathers all the file descriptors, + * figures out which want to write and read, asks the OS which ones + * are available, and for those file descriptors, tries to do the + * reads/writes we've asked it. It handles retry in the case where a + * read or write is done partially. + * + * Since the OS does buffering internally (on my system, over 100k + * worth) writes will normally succeed immediately. However, if the + * client is slow or malicious, and doesn't read from the socket as + * fast as we're writing, eventually the socket buffer will fill up; + * we don't care, because ccan/io will wait until there's room to + * write this reply before it will read again. The client just hurts + * themselves, and there's no Denial of Service on us. + * + * If we were to queue outgoing messages ourselves, we *would* have to + * consider such scenarios; this is why our daemons generally avoid + * buffering from untrusted parties. */ + return io_write_wire(conn, msg_out, client_read_next, c); +} + +/* The c-lightning testing framework imbues the hsm_secret with a + * file created before hsmd starts. For now we use the secret from + * the testing framework rather than generating in the remote signer. + * + * Returns true if test seed fetched. If false is returned test seed not + * present, use random instead. + */ +static bool read_test_seed(struct secret *hsm_secret) +{ + struct stat st; + int fd = open("hsm_secret", O_RDONLY); + if (fd < 0) + return false; + if (stat("hsm_secret", &st) != 0) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "stating: %s", strerror(errno)); + + /* If the seed is stored in clear. */ + if (st.st_size > 32) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "hsm_secret not in clear"); + + if (!read_all(fd, hsm_secret, sizeof(*hsm_secret))) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "reading: %s", strerror(errno)); + close(fd); + return true; +} + +// TODO - Add support for bolt12 to remote signer and remove this +// entire routine. This does not actually setup a usable BOLT12 +// context; it always uses an empty hsm_secret. +static void bogus_bolt12_placeholder(struct point32 *bolt12out) +{ + struct secret bad_hsm_secret; + u8 bip32_seed[BIP32_ENTROPY_LEN_256]; + u32 salt = 0; + struct ext_key master_extkey, child_extkey; + secp256k1_keypair bolt12; + + // This needs to be computed on the remote server! + memset(&bad_hsm_secret, 0, sizeof(bad_hsm_secret)); + + /* Fill in the BIP32 tree for bitcoin addresses. */ + /* In libwally-core, the version BIP32_VER_TEST_PRIVATE is for testnet/regtest, + * and BIP32_VER_MAIN_PRIVATE is for mainnet. For litecoin, we also set it like + * bitcoin else.*/ + do { + hkdf_sha256(bip32_seed, sizeof(bip32_seed), + &salt, sizeof(salt), + &bad_hsm_secret, + sizeof(bad_hsm_secret), + "bip32 seed", strlen("bip32 seed")); + salt++; + } while (bip32_key_from_seed(bip32_seed, sizeof(bip32_seed), + bip32_key_version.bip32_privkey_version, + 0, &master_extkey) != WALLY_OK); + + if (bip32_key_from_parent(&master_extkey, + BIP32_INITIAL_HARDENED_CHILD|9735, + BIP32_FLAG_KEY_PRIVATE, + &child_extkey) != WALLY_OK) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Can't derive bolt12 bip32 key"); + + /* libwally says: The private key with prefix byte 0; remove it + * for libsecp256k1. */ + if (secp256k1_keypair_create(secp256k1_ctx, &bolt12, + child_extkey.priv_key+1) != 1) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Can't derive bolt12 keypair"); + + /* We also give it the base key for bolt12 payerids */ + if (secp256k1_keypair_xonly_pub(secp256k1_ctx, &bolt12out->pubkey, NULL, + &bolt12) != 1) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Could derive bolt12 public key."); +} + +// TODO - Add support for onion_reply_secret to remote signer and remove this +// entire routine. This does not actually setup a usable onion_reply_secret +// context; it always uses an empty hsm_secret. +static void bogus_onion_reply_secret_placeholder(struct secret *onion_reply_secret) +{ + // This needs to be computed on the remote server! + memset(&onion_reply_secret, 0, sizeof(onion_reply_secret)); +} + +static void persist_node_id(const struct node_id *node_id) +{ + char *node_id_str = tal_fmt(tmpctx, "%s\n", + type_to_string(tmpctx, struct node_id, node_id)); + int fd = open("NODE_ID", O_WRONLY|O_TRUNC|O_CREAT, 0666); + assert(fd != -1); + write_all(fd, node_id_str, strlen(node_id_str)); + close(fd); +} + +static bool restore_node_id(struct node_id *node_id) +{ + if (access("NODE_ID", F_OK) == -1) { + // This is a cold start, we don't have a node_id yet. + return false; + } + + char *buffer = grab_file(tmpctx, "NODE_ID"); + assert(buffer != NULL); + size_t len = tal_bytelen(buffer) - 2; + assert(buffer[len] == '\n'); + bool ok = node_id_from_hexstr(buffer, len, node_id); + assert(ok); + return true; +} + +/*~ This is the response to lightningd's HSM_INIT request, which is the first + * thing it sends. */ +static struct io_plan *init_hsm(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + struct node_id node_id; + struct point32 bolt12; + struct secret onion_reply_secret; + struct privkey *force_privkey; + struct secret *force_bip32_seed; + struct secrets *force_channel_secrets; + struct sha256 *force_channel_secrets_shaseed; + struct secret *hsm_encryption_key; + struct secret hsm_secret; + struct secret *use_hsm_secret; + bool coldstart; + + /* This must be lightningd. */ + assert(is_lightningd(c)); + + /*~ The fromwire_* routines are autogenerated, based on the message + * definitions in hsm_client_wire.csv. The format of those files is + * an extension of the simple comma-separated format output by the + * BOLT tools/extract-formats.py tool. */ + if (!fromwire_hsmd_init(NULL, msg_in, &bip32_key_version, &chainparams, + &hsm_encryption_key, &force_privkey, + &force_bip32_seed, &force_channel_secrets, + &force_channel_secrets_shaseed)) + return bad_req(conn, c, msg_in); + +#if DEVELOPER + dev_force_privkey = force_privkey; + dev_force_bip32_seed = force_bip32_seed; + dev_force_channel_secrets = force_channel_secrets; + dev_force_channel_secrets_shaseed = force_channel_secrets_shaseed; +#endif + + // We can't force any of these secrets individually, we only + // can set the seed (for testnet integration tests). If we + // see anything being set fail fast. + assert(force_privkey == NULL); + assert(force_bip32_seed == NULL); + assert(force_channel_secrets == NULL); + assert(force_channel_secrets_shaseed == NULL); + + /* The hsm_encryption_key doesn't make any sense with the + * remote signer, fail-fast if it's set. + */ + assert(hsm_encryption_key == NULL); + + /* Once we have read the init message we know which params the master + * will use */ + c->chainparams = chainparams; + + /* Is this a warm start (restart) or a cold start (first time)? */ + if (restore_node_id(&node_id)) { + // This is a warm start. + proxy_set_node_id(&node_id); + } else { + status_unusual("cold start, initializing the remote signer"); + + // This is a cold start, initialize the remote signer. + + /* To support integration tests we honor any seed provided + * in the hsm_secret file (testnet only). Otherwise we + * generate a random seed. + */ + if (read_test_seed(&hsm_secret)) { + // We are running integration tests and the secret has been forced. + use_hsm_secret = &hsm_secret; + } else { + // We are not running integration tests, remote signer generates. + use_hsm_secret = NULL; + } + + coldstart = true; // this can go away in the API. + proxy_stat rv = proxy_init_hsm(&bip32_key_version, chainparams, + coldstart, use_hsm_secret, + &node_id); + if (PROXY_PERMANENT(rv)) { + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + } + else if (!PROXY_SUCCESS(rv)) { + status_unusual("proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + } + + /* Mark this node as already inited. */ + persist_node_id(&node_id); + } + + // Fetch the bip32 ext_pub_key. + proxy_stat rv = proxy_get_ext_pub_key(&pubstuff.bip32); + if (PROXY_PERMANENT(rv)) { + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + } + else if (!PROXY_SUCCESS(rv)) { + status_unusual("proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + } + + // TODO - add support for bolt12 + bogus_bolt12_placeholder(&bolt12); + + // TODO - add support for onion_reply_secret + bogus_onion_reply_secret_placeholder(&onion_reply_secret); + + /* Now we can consider ourselves initialized, and we won't get + * upset if we get a non-init message. */ + initialized = true; + + return req_reply(conn, c, + take(towire_hsmd_init_reply(NULL, &node_id, + &pubstuff.bip32, + &bolt12, &onion_reply_secret))); +} + +/*~ The client has asked us to extract the shared secret from an EC Diffie + * Hellman token. This doesn't leak any information, but requires the private + * key, so the hsmd performs it. It's used to set up an encryption key for the + * connection handshaking (BOLT #8) and for the onion wrapping (BOLT #4). */ +static struct io_plan *handle_ecdh(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + struct pubkey point; + struct secret ss; + + if (!fromwire_hsmd_ecdh_req(msg_in, &point)) + return bad_req(conn, c, msg_in); + + proxy_stat rv = proxy_handle_ecdh(&point, &ss); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + /*~ In the normal case, we return the shared secret, and then read + * the next msg. */ + return req_reply(conn, c, take(towire_hsmd_ecdh_resp(NULL, &ss))); +} + +/*~ The specific routine to sign the channel_announcement message. This is + * defined in BOLT #7, and requires *two* signatures: one from this node's key + * (to prove it's from us), and one from the bitcoin key used to create the + * funding transaction (to prove we own the output). */ +static struct io_plan *handle_cannouncement_sig(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + /*~ Our autogeneration code doesn't define field offsets, so we just + * copy this from the spec itself. + * + * Note that 'check-source' will actually find and check this quote + * against the spec (if available); whitespace is ignored and + * "..." means some content is skipped, but it works remarkably well to + * track spec changes. */ + + /* BOLT #7: + * + * - MUST compute the double-SHA256 hash `h` of the message, beginning + * at offset 256, up to the end of the message. + * - Note: the hash skips the 4 signatures but hashes the rest of the + * message, including any future fields appended to the end. + */ + /* First type bytes are the msg type */ + size_t offset = 2 + 256; + secp256k1_ecdsa_signature node_sig, bitcoin_sig; + u8 *reply; + u8 *ca; + + /*~ You'll find FIXMEs like this scattered through the code. + * Sometimes they suggest simple improvements which someone like + * yourself should go ahead an implement. Sometimes they're deceptive + * quagmires which will cause you nothing but grief. You decide! */ + + /*~ Christian uses TODO(cdecker) or FIXME(cdecker), but I'm sure he won't + * mind if you fix this for him! */ + + /*~ fromwire_ routines which need to do allocation take a tal context + * as their first field; tmpctx is good here since we won't need it + * after this function. */ + if (!fromwire_hsmd_cannouncement_sig_req(tmpctx, msg_in, &ca)) + return bad_req(conn, c, msg_in); + + if (tal_count(ca) < offset) + return bad_req_fmt(conn, c, msg_in, + "bad cannounce length %zu", + tal_count(ca)); + + if (fromwire_peektype(ca) != WIRE_CHANNEL_ANNOUNCEMENT) + return bad_req_fmt(conn, c, msg_in, + "Invalid channel announcement"); + + proxy_stat rv = proxy_handle_cannouncement_sig( + &c->id, c->dbid, ca, + &node_sig, &bitcoin_sig + ); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + reply = towire_hsmd_cannouncement_sig_reply(NULL, &node_sig, + &bitcoin_sig); + return req_reply(conn, c, take(reply)); +} + +/*~ The specific routine to sign the channel_update message. */ +static struct io_plan *handle_channel_update_sig(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + /* BOLT #7: + * + * - MUST set `signature` to the signature of the double-SHA256 of the + * entire remaining packet after `signature`, using its own + * `node_id`. + */ + /* 2 bytes msg type + 64 bytes signature */ + size_t offset = 66; + secp256k1_ecdsa_signature sig; + struct short_channel_id scid; + u32 timestamp, fee_base_msat, fee_proportional_mill; + struct amount_msat htlc_minimum, htlc_maximum; + u8 message_flags, channel_flags; + u16 cltv_expiry_delta; + struct bitcoin_blkid chain_hash; + u8 *cu; + + if (!fromwire_hsmd_cupdate_sig_req(tmpctx, msg_in, &cu)) + return bad_req(conn, c, msg_in); + + if (!fromwire_channel_update_option_channel_htlc_max(cu, &sig, + &chain_hash, &scid, ×tamp, &message_flags, + &channel_flags, &cltv_expiry_delta, + &htlc_minimum, &fee_base_msat, + &fee_proportional_mill, &htlc_maximum)) { + return bad_req_fmt(conn, c, msg_in, "Bad inner channel_update"); + } + if (tal_count(cu) < offset) + return bad_req_fmt(conn, c, msg_in, + "inner channel_update too short"); + + proxy_stat rv = proxy_handle_channel_update_sig(cu, &sig); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + cu = towire_channel_update_option_channel_htlc_max(tmpctx, &sig, &chain_hash, + &scid, timestamp, message_flags, channel_flags, + cltv_expiry_delta, htlc_minimum, + fee_base_msat, fee_proportional_mill, + htlc_maximum); + return req_reply(conn, c, take(towire_hsmd_cupdate_sig_reply(NULL, cu))); +} + +/*~ This gets the basepoints for a channel; it's not private information really + * (we tell the peer this to establish a channel, as it sets up the keys used + * for each transaction). + * + * Note that this is asked by lightningd, so it tells us what channels it wants. + */ +static struct io_plan *handle_get_channel_basepoints(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + struct node_id peer_id; + u64 dbid; + struct basepoints basepoints; + struct pubkey funding_pubkey; + + if (!fromwire_hsmd_get_channel_basepoints(msg_in, &peer_id, &dbid)) + return bad_req(conn, c, msg_in); + + proxy_stat rv = proxy_handle_get_channel_basepoints( + &peer_id, dbid, &basepoints, &funding_pubkey); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + return req_reply(conn, c, + take(towire_hsmd_get_channel_basepoints_reply(NULL, + &basepoints, + &funding_pubkey))); +} + +/*~ This is another lightningd-only interface; signing a commit transaction. + * This is dangerous, since if we sign a revoked commitment tx we'll lose + * funds, thus it's only available to lightningd. + * + * + * Oh look, another FIXME! */ +/* FIXME: Ensure HSM never does this twice for same dbid! */ +static struct io_plan *handle_sign_commitment_tx(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + struct pubkey remote_funding_pubkey; + struct node_id peer_id; + u64 dbid; + struct bitcoin_tx *tx; + u64 commit_num; + struct bitcoin_signature sig; + + if (!fromwire_hsmd_sign_commitment_tx(tmpctx, msg_in, + &peer_id, &dbid, + &tx, + &remote_funding_pubkey, + &commit_num)) + return bad_req(conn, c, msg_in); + + tx->chainparams = c->chainparams; + + /* Basic sanity checks. */ + if (tx->wtx->num_inputs != 1) + return bad_req_fmt(conn, c, msg_in, "tx must have 1 input"); + if (tx->wtx->num_outputs == 0) + return bad_req_fmt(conn, c, msg_in, "tx must have > 0 outputs"); + + // WORKAROUND - sometimes c-lightning calls handle_sign_commitment_tx + // with mutual close transactions. We can tell the difference because + // the locktime field will be set to 0 for a mutual close. + if (tx->wtx->locktime == 0) { + // This is really a mutual close. + fprintf(stderr, + "handle_sign_commitment_tx called with locktime==0; " + "dispatching to proxy_handle_sign_mutual_close_tx instead\n"); + proxy_stat rv = proxy_handle_sign_mutual_close_tx( + tx, &remote_funding_pubkey, &peer_id, dbid, &sig); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + } else { + // This is a unilateral close from our side. + proxy_stat rv = proxy_handle_sign_commitment_tx( + &peer_id, dbid, commit_num, &sig); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + } + + return req_reply(conn, c, + take(towire_hsmd_sign_commitment_tx_reply(NULL, &sig))); +} + +/* Validate the peer's signatures for our commitment and htlc txs. */ +static struct io_plan *handle_validate_commitment_tx(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + struct bitcoin_tx *tx; + struct simple_htlc **htlc; + u64 commit_num; + u32 feerate; + struct bitcoin_signature commit_sig; + struct bitcoin_signature *htlc_sigs; + struct secret *old_secret; + struct pubkey next_per_commitment_point; + + if (!fromwire_hsmd_validate_commitment_tx(tmpctx, msg_in, + &tx, &htlc, + &commit_num, &feerate, + &commit_sig, &htlc_sigs)) + bad_req(conn, c, msg_in); + + proxy_stat rv = proxy_handle_validate_commitment_tx( + tx, + &c->id, c->dbid, + htlc, commit_num, feerate, + &commit_sig, htlc_sigs, + &old_secret, &next_per_commitment_point); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + return req_reply(conn, c, + take(towire_hsmd_validate_commitment_tx_reply( + NULL, old_secret, &next_per_commitment_point))); +} + +/* Validate the peer's signatures for our commitment and htlc txs. */ +static struct io_plan *handle_validate_revocation(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + u64 revoke_num; + struct secret old_secret; + + if (!fromwire_hsmd_validate_revocation(msg_in, + &revoke_num, &old_secret)) + bad_req(conn, c, msg_in); + + proxy_stat rv = proxy_handle_validate_revocation( + &c->id, c->dbid, + revoke_num, &old_secret); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + return req_reply(conn, c, + take(towire_hsmd_validate_revocation_reply(NULL))); +} + +/*~ This is used by channeld to create signatures for the remote peer's + * commitment transaction. It's functionally identical to signing our own, + * but we expect to do this repeatedly as commitment transactions are + * updated. + * + * The HSM almost certainly *should* do more checks before signing! + */ +/* FIXME: make sure it meets some criteria? */ +static struct io_plan *handle_sign_remote_commitment_tx(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + struct pubkey remote_funding_pubkey; + struct bitcoin_tx *tx; + struct bitcoin_signature sig; + struct pubkey remote_per_commit; + bool option_static_remotekey; + u64 commit_num; + struct simple_htlc **htlc; + u32 feerate; + + if (!fromwire_hsmd_sign_remote_commitment_tx(tmpctx, msg_in, + &tx, + &remote_funding_pubkey, + &remote_per_commit, + &option_static_remotekey, + &commit_num, + &htlc, &feerate)) + bad_req(conn, c, msg_in); + tx->chainparams = c->chainparams; + + /* Basic sanity checks. */ + if (tx->wtx->num_inputs != 1) + return bad_req_fmt(conn, c, msg_in, "tx must have 1 input"); + if (tx->wtx->num_outputs == 0) + return bad_req_fmt(conn, c, msg_in, "tx must have > 0 outputs"); + + proxy_stat rv = proxy_handle_sign_remote_commitment_tx( + tx, &remote_funding_pubkey, + &c->id, c->dbid, + &remote_per_commit, + htlc, commit_num, feerate, + &sig); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + STATUS_DEBUG("%s:%d %s: signature: %s", + __FILE__, __LINE__, __FUNCTION__, + type_to_string(tmpctx, struct bitcoin_signature, &sig)); + + return req_reply(conn, c, take(towire_hsmd_sign_tx_reply(NULL, &sig))); +} + +/*~ This is used by channeld to create signatures for the remote peer's + * HTLC transactions. */ +static struct io_plan *handle_sign_remote_htlc_tx(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + struct bitcoin_tx *tx; + struct bitcoin_signature sig; + struct pubkey remote_per_commit_point; + u8 *wscript; + bool option_anchor_outputs; + + if (!fromwire_hsmd_sign_remote_htlc_tx(tmpctx, msg_in, + &tx, &wscript, + &remote_per_commit_point, + &option_anchor_outputs)) + return bad_req(conn, c, msg_in); + tx->chainparams = c->chainparams; + + // The remote signer already knows opt_anchor_outputs from the + // ready channel call. + + proxy_stat rv = proxy_handle_sign_remote_htlc_tx( + tx, + wscript, + &remote_per_commit_point, + &c->id, + c->dbid, + &sig); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + return req_reply(conn, c, take(towire_hsmd_sign_tx_reply(NULL, &sig))); +} + +/*~ When we send a commitment transaction onchain (unilateral close), there's + * a delay before we can spend it. onchaind does an explicit transaction to + * transfer it to the wallet so that doesn't need to remember how to spend + * this complex transaction. */ +static struct io_plan *handle_sign_delayed_payment_to_us(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + u64 commit_num; + struct bitcoin_tx *tx; + u8 *wscript; + + /*~ We don't derive the wscript ourselves, but perhaps we should? */ + if (!fromwire_hsmd_sign_delayed_payment_to_us(tmpctx, msg_in, + &commit_num, + &tx, &wscript)) + return bad_req(conn, c, msg_in); + tx->chainparams = c->chainparams; + + struct bitcoin_signature sig; + proxy_stat rv = proxy_handle_sign_delayed_payment_to_us( + tx, commit_num, wscript, &c->id, c->dbid, &sig); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + return req_reply(conn, c, take(towire_hsmd_sign_tx_reply(NULL, &sig))); +} + +/*~ This is used when a commitment transaction is onchain, and has an HTLC + * output paying to us (because we have the preimage); this signs that + * transaction, which lightningd will broadcast to collect the funds. */ +static struct io_plan *handle_sign_remote_htlc_to_us(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + struct bitcoin_tx *tx; + struct pubkey remote_per_commitment_point; + u8 *wscript; + bool option_anchor_outputs; + + if (!fromwire_hsmd_sign_remote_htlc_to_us(tmpctx, msg_in, + &remote_per_commitment_point, + &tx, &wscript, + &option_anchor_outputs)) + return bad_req(conn, c, msg_in); + + tx->chainparams = c->chainparams; + + // The remote signer already knows opt_anchor_outputs from the + // ready channel call. + + struct bitcoin_signature sig; + proxy_stat rv = proxy_handle_sign_remote_htlc_to_us( + tx, + wscript, + &remote_per_commitment_point, + &c->id, + c->dbid, + &sig); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + return req_reply(conn, c, take(towire_hsmd_sign_tx_reply(NULL, &sig))); +} + +/*~ This is used when the remote peer's commitment transaction is revoked; + * we can use the revocation secret to spend the outputs. For simplicity, + * we do them one at a time, though. */ +static struct io_plan *handle_sign_penalty_to_us(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + struct secret revocation_secret; + struct bitcoin_tx *tx; + u8 *wscript; + + if (!fromwire_hsmd_sign_penalty_to_us(tmpctx, msg_in, + &revocation_secret, + &tx, &wscript)) + return bad_req(conn, c, msg_in); + tx->chainparams = c->chainparams; + + struct bitcoin_signature sig; + proxy_stat rv = proxy_handle_sign_penalty_to_us( + tx, &revocation_secret, wscript, &c->id, c->dbid, &sig); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + return req_reply(conn, c, take(towire_hsmd_sign_tx_reply(NULL, &sig))); +} + +/*~ This is used when a commitment transaction is onchain, and has an HTLC + * output paying to them, which has timed out; this signs that transaction, + * which lightningd will broadcast to collect the funds. */ +static struct io_plan *handle_sign_local_htlc_tx(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + u64 commit_num; + struct bitcoin_tx *tx; + u8 *wscript; + struct bitcoin_signature sig; + bool option_anchor_outputs; + + if (!fromwire_hsmd_sign_local_htlc_tx(tmpctx, msg_in, + &commit_num, &tx, &wscript, + &option_anchor_outputs)) + return bad_req(conn, c, msg_in); + + tx->chainparams = c->chainparams; + + // The remote signer already knows opt_anchor_outputs from the + // ready channel call. + + proxy_stat rv = proxy_handle_sign_local_htlc_tx( + tx, commit_num, wscript, &c->id, c->dbid, &sig); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + return req_reply(conn, c, take(towire_hsmd_sign_tx_reply(NULL, &sig))); +} + +/*~ This get the Nth a per-commitment point, and for N > 2, returns the + * grandparent per-commitment secret. This pattern is because after + * negotiating commitment N-1, we send them the next per-commitment point, + * and reveal the previous per-commitment secret as a promise not to spend + * the previous commitment transaction. */ +static struct io_plan *handle_get_per_commitment_point(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + struct pubkey per_commitment_point; + u64 n; + struct secret *old_secret; + + if (!fromwire_hsmd_get_per_commitment_point(msg_in, &n)) + return bad_req(conn, c, msg_in); + + proxy_stat rv = proxy_handle_get_per_commitment_point( + &c->id, c->dbid, n, + &per_commitment_point, &old_secret); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + /*~ hsm_client_wire.csv marks the secret field here optional, so it only + * gets included if the parameter is non-NULL. We violate 80 columns + * pretty badly here, but it's a recommendation not a religion. */ + return req_reply(conn, c, + take(towire_hsmd_get_per_commitment_point_reply(NULL, + &per_commitment_point, + old_secret))); +} + +/*~ This is used when the remote peer claims to have knowledge of future + * commitment states (option_data_loss_protect in the spec) which means we've + * been restored from backup or something, and may have already revealed + * secrets. We carefully check that this is true, here. */ +static struct io_plan *handle_check_future_secret(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + u64 n; + struct secret suggested; + + if (!fromwire_hsmd_check_future_secret(msg_in, &n, &suggested)) + return bad_req(conn, c, msg_in); + + bool correct; + proxy_stat rv = proxy_handle_check_future_secret( + &c->id, c->dbid, n, &suggested, &correct); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + return req_reply(conn, c, + take(towire_hsmd_check_future_secret_reply(NULL, + correct))); +} + +/* This is used by closingd to sign off on a mutual close tx. */ +static struct io_plan *handle_sign_mutual_close_tx(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + struct bitcoin_tx *tx; + struct pubkey remote_funding_pubkey; + struct bitcoin_signature sig; + + if (!fromwire_hsmd_sign_mutual_close_tx(tmpctx, msg_in, + &tx, + &remote_funding_pubkey)) + return bad_req(conn, c, msg_in); + + tx->chainparams = c->chainparams; + + proxy_stat rv = proxy_handle_sign_mutual_close_tx( + tx, &remote_funding_pubkey, &c->id, c->dbid, &sig); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + return req_reply(conn, c, take(towire_hsmd_sign_tx_reply(NULL, &sig))); +} + +/*~ Since we process requests then service them in strict order, and because + * only lightningd can request a new client fd, we can get away with a global + * here! But because we are being tricky, I set it to an invalid value when + * not in use, and sprinkle assertions around. */ +static int pending_client_fd = -1; + +/*~ This is the callback from below: having sent the reply, we now send the + * fd for the client end of the new socketpair. */ +static struct io_plan *send_pending_client_fd(struct io_conn *conn, + struct client *master) +{ + int fd = pending_client_fd; + /* This must be the master. */ + assert(is_lightningd(master)); + assert(fd != -1); + + /* This sanity check shouldn't be necessary, but it's cheap. */ + pending_client_fd = -1; + + /*~There's arcane UNIX magic to send an open file descriptor over a + * UNIX domain socket. There's no great way to autogenerate this + * though; especially for the receive side, so we always pass these + * manually immediately following the message. + * + * io_send_fd()'s third parameter is whether to close the local one + * after sending; that saves us YA callback. + */ + return io_send_fd(conn, fd, true, client_read_next, master); +} + +/*~ This is used by the master to create a new client connection (which + * becomes the HSM_FD for the subdaemon after forking). */ +static struct io_plan *pass_client_hsmfd(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + int fds[2]; + u64 dbid, capabilities; + struct node_id id; + + /* This must be lightningd itself. */ + assert(is_lightningd(c)); + + if (!fromwire_hsmd_client_hsmfd(msg_in, &id, &dbid, &capabilities)) + return bad_req(conn, c, msg_in); + + /* socketpair is a bi-directional pipe, which is what we want. */ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) + status_failed(STATUS_FAIL_INTERNAL_ERROR, "creating fds: %s", + strerror(errno)); + + STATUS_DEBUG("new_client: %"PRIu64, dbid); + new_client(c, c->chainparams, &id, dbid, capabilities, fds[0]); + + // Skip zero dbid (master, gossipd, connectd). + if (dbid != 0) { + proxy_stat rv = proxy_handle_pass_client_hsmfd(&id, dbid, capabilities); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + } + + /*~ We stash this in a global, because we need to get both the fd and + * the client pointer to the callback. The other way would be to + * create a boutique structure and hand that, but we don't need to. */ + pending_client_fd = fds[1]; + return io_write_wire(conn, take(towire_hsmd_client_hsmfd_reply(NULL)), + send_pending_client_fd, c); +} + +/*~ This is used to declare a new channel. */ +static struct io_plan *handle_new_channel(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + struct node_id peer_id; + u64 dbid; + + if (!fromwire_hsmd_new_channel(msg_in, &peer_id, &dbid)) + return bad_req(conn, c, msg_in); + + proxy_stat rv = proxy_handle_new_channel(&peer_id, dbid); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + return req_reply(conn, c, + take(towire_hsmd_new_channel_reply(NULL))); +} + +/*~ This is used to provide all unchanging public channel parameters. */ +static struct io_plan *handle_ready_channel(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + bool is_outbound; + struct amount_sat channel_value; + struct amount_msat push_value; + struct bitcoin_txid funding_txid; + u16 funding_txout; + u16 local_to_self_delay; + u8 *local_shutdown_script; + u32 *local_shutdown_wallet_index; + struct basepoints remote_basepoints; + struct pubkey remote_funding_pubkey; + u16 remote_to_self_delay; + u8 *remote_shutdown_script; + struct channel_type *channel_type; + + if (!fromwire_hsmd_ready_channel(tmpctx, msg_in, &is_outbound, + &channel_value, &push_value, &funding_txid, + &funding_txout, &local_to_self_delay, + &local_shutdown_script, + &local_shutdown_wallet_index, + &remote_basepoints, + &remote_funding_pubkey, + &remote_to_self_delay, + &remote_shutdown_script, + &channel_type)) + return bad_req(conn, c, msg_in); + + proxy_stat rv = proxy_handle_ready_channel( + &c->id, c->dbid, + is_outbound, + &channel_value, + &push_value, + &funding_txid, + funding_txout, + local_to_self_delay, // locally imposed on counterparty to_self outputs + local_shutdown_script, + local_shutdown_wallet_index, + &remote_basepoints, + &remote_funding_pubkey, + remote_to_self_delay, // counterparty imposed on our to_self outputs + remote_shutdown_script, + channel_type); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + return req_reply(conn, c, + take(towire_hsmd_ready_channel_reply(NULL))); +} + +/*~ lightningd asks us to sign a withdrawal; same as above but in theory + * we can do more to check the previous case is valid. */ +static struct io_plan *handle_sign_withdrawal_tx(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + struct utxo **utxos; + struct wally_psbt *psbt; + + if (!fromwire_hsmd_sign_withdrawal(tmpctx, msg_in, + &utxos, &psbt)) + return bad_req(conn, c, msg_in); + + struct bitcoin_tx_output **outputs; + outputs = tal_arr(tmpctx, struct bitcoin_tx_output *, psbt->num_outputs); + for (size_t ii = 0; ii < psbt->num_outputs; ++ii) { + outputs[ii] = tal(outputs, struct bitcoin_tx_output); + outputs[ii]->amount.satoshis = psbt->tx->outputs[ii].satoshi; /* Raw: from wally_tx_output */ + outputs[ii]->script = + tal_dup_arr(outputs[ii], u8, + psbt->tx->outputs[ii].script, + psbt->tx->outputs[ii].script_len, 0); + } + + u8 *** wits; + proxy_stat rv = proxy_handle_sign_withdrawal_tx( + outputs, utxos, psbt, &wits); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + /* We must have one witness element for each input. */ + assert(tal_count(wits) == psbt->num_inputs); + + /* Witnesses and inputs are in the same order. */ + for (size_t kk = 0; kk < tal_count(wits); ++kk) { + struct wally_psbt_input *input = &psbt->inputs[kk]; + u8 *sig = wits[kk][0]; + u8 *pubkey = wits[kk][1]; + int ret; + + /* If this is a PSBT, we should skip any inputs that + * have an empty signature. */ + if (tal_count(sig) == 0) + continue; + + struct pubkey spubkey; + pubkey_from_der(pubkey, EC_PUBLIC_KEY_LEN, &spubkey); + psbt_input_add_pubkey(psbt, kk, &spubkey); + + tal_wally_start(); + ret = wally_psbt_input_add_signature( + input, + pubkey, EC_PUBLIC_KEY_LEN, + sig, tal_count(sig)); + assert(ret == WALLY_OK); + tal_wally_end(psbt); + } + + return req_reply(conn, c, + take(towire_hsmd_sign_withdrawal_reply(NULL, psbt))); +} + +static struct io_plan *handle_get_output_scriptpubkey(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + struct unilateral_close_info info; + + info.commitment_point = NULL; + if (!fromwire_hsmd_get_output_scriptpubkey(tmpctx, msg_in, + &info.channel_id, + &info.peer_id, + &info.commitment_point)) + return bad_req(conn, c, msg_in); + + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "handle_get_output_scriptpubkey unimplemented"); + + return bad_req_fmt(conn, c, msg_in, + "handle_get_output_scriptpubkey unimplemented"); +} + +/*~ Lightning invoices, defined by BOLT 11, are signed. This has been + * surprisingly controversial; it means a node needs to be online to create + * invoices. However, it seems clear to me that in a world without + * intermedaries you need proof that you have received an offer (the + * signature), as well as proof that you've paid it (the preimage). */ +static struct io_plan *handle_sign_invoice(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + /*~ We make up a 'u5' type to represent BOLT11's 5-bits-per-byte + * format: it's only for human consumption, as typedefs are almost + * entirely transparent to the C compiler. */ + u5 *u5bytes; + u8 *hrpu8; + secp256k1_ecdsa_recoverable_signature rsig; + + if (!fromwire_hsmd_sign_invoice(tmpctx, msg_in, &u5bytes, &hrpu8)) + return bad_req(conn, c, msg_in); + + proxy_stat rv = proxy_handle_sign_invoice(u5bytes, hrpu8, &rsig); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + return req_reply(conn, c, + take(towire_hsmd_sign_invoice_reply(NULL, &rsig))); +} + +/*~ It's optional for nodes to send node_announcement, but it lets us set our + * favourite color and cool alias! Plus other minor details like how to + * connect to us. */ +static struct io_plan *handle_sign_node_announcement(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + /* BOLT #7: + * + * The origin node: + *... + * - MUST set `signature` to the signature of the double-SHA256 of the + * entire remaining packet after `signature` (using the key given by + * `node_id`). + */ + /* 2 bytes msg type + 64 bytes signature */ + size_t offset = 66; + secp256k1_ecdsa_signature sig; + u8 *reply; + u8 *ann; + + if (!fromwire_hsmd_node_announcement_sig_req(tmpctx, msg_in, &ann)) + return bad_req(conn, c, msg_in); + + if (tal_count(ann) < offset) + return bad_req_fmt(conn, c, msg_in, + "Node announcement too short"); + + if (fromwire_peektype(ann) != WIRE_NODE_ANNOUNCEMENT) + return bad_req_fmt(conn, c, msg_in, + "Invalid announcement"); + + proxy_stat rv = proxy_handle_sign_node_announcement(ann, &sig); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + reply = towire_hsmd_node_announcement_sig_reply(NULL, &sig); + return req_reply(conn, c, take(reply)); +} + +/*~ lightningd asks us to sign a message. I tweeted the spec + * in https://twitter.com/rusty_twit/status/1182102005914800128: + * + * @roasbeef & @bitconner point out that #lnd algo is: + * zbase32(SigRec(SHA256(SHA256("Lightning Signed Message:" + msg)))). + * zbase32 from https://philzimmermann.com/docs/human-oriented-base-32-encoding.txt + * and SigRec has first byte 31 + recovery id, followed by 64 byte sig. #specinatweet + */ +static struct io_plan *handle_sign_message(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + u8 *msg; + secp256k1_ecdsa_recoverable_signature rsig; + + if (!fromwire_hsmd_sign_message(tmpctx, msg_in, &msg)) + return bad_req(conn, c, msg_in); + + proxy_stat rv = proxy_handle_sign_message(msg, &rsig); + if (PROXY_PERMANENT(rv)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "proxy_%s failed: %s", __FUNCTION__, + proxy_last_message()); + else if (!PROXY_SUCCESS(rv)) + return bad_req_fmt(conn, c, msg_in, + "proxy_%s error: %s", __FUNCTION__, + proxy_last_message()); + + return req_reply(conn, c, + take(towire_hsmd_sign_message_reply(NULL, &rsig))); +} + +/*~ lightningd asks us to sign a liquidity ad offer */ +static struct io_plan *handle_sign_option_will_fund_offer(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + struct pubkey funding_pubkey; + u32 lease_expiry, channel_fee_base_max_msat; + u16 channel_fee_max_ppt; + + if (!fromwire_hsmd_sign_option_will_fund_offer(msg_in, + &funding_pubkey, + &lease_expiry, + &channel_fee_base_max_msat, + &channel_fee_max_ppt)) + return bad_req(conn, c, msg_in); + + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "handle_sign_option_will_fund_offer unimplemented"); + return bad_req_fmt(conn, c, msg_in, + "handle_sign_option_will_fund_offer unimplemented"); +} + +/*~ lightningd asks us to sign a bolt12 (e.g. offer). */ +static struct io_plan *handle_sign_bolt12(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + char *messagename, *fieldname; + struct sha256 merkle; + u8 *publictweak; + + if (!fromwire_hsmd_sign_bolt12(tmpctx, msg_in, + &messagename, &fieldname, &merkle, + &publictweak)) + return bad_req(conn, c, msg_in); + + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "handle_sign_bolt12 unimplemented"); + return bad_req_fmt(conn, c, msg_in, + "handle_sign_bolt12 unimplemented"); +} + +#if DEVELOPER +static struct io_plan *handle_memleak(struct io_conn *conn, + struct client *c, + const u8 *msg_in) +{ + struct htable *memtable; + bool found_leak; + u8 *reply; + + memtable = memleak_find_allocations(tmpctx, msg_in, msg_in); + + /* Now delete clients and anything they point to. */ + memleak_remove_region(memtable, + dbid_zero_clients, sizeof(dbid_zero_clients)); + memleak_remove_uintmap(memtable, &clients); + memleak_remove_region(memtable, + status_conn, tal_bytelen(status_conn)); + + memleak_remove_pointer(memtable, dev_force_privkey); + memleak_remove_pointer(memtable, dev_force_bip32_seed); + + found_leak = dump_memleak(memtable, memleak_status_broken); + reply = towire_hsmd_dev_memleak_reply(NULL, found_leak); + return req_reply(conn, c, take(reply)); +} +#endif /* DEVELOPER */ + +/*~ This routine checks that a client is allowed to call the handler. */ +static bool check_client_capabilities(struct client *client, + enum hsmd_wire t) +{ + /*~ Here's a useful trick: enums in C are not real types, they're + * semantic sugar sprinkled over an int, bascally (in fact, older + * versions of gcc used to convert the values ints in the parser!). + * + * But GCC will do one thing for us: if we have a switch statement + * with a controlling expression which is an enum, it will warn us + * if a declared enum value is *not* handled in the switch, eg: + * enumeration value ‘FOOBAR’ not handled in switch [-Werror=switch] + * + * This only works if there's no 'default' label, which is sometimes + * hard, as we *can* have non-enum values in our enum. But the tradeoff + * is worth it so the compiler tells us everywhere we have to fix when + * we add a new enum identifier! + */ + switch (t) { + case WIRE_HSMD_ECDH_REQ: + return (client->capabilities & HSM_CAP_ECDH) != 0; + + case WIRE_HSMD_CANNOUNCEMENT_SIG_REQ: + case WIRE_HSMD_CUPDATE_SIG_REQ: + case WIRE_HSMD_NODE_ANNOUNCEMENT_SIG_REQ: + return (client->capabilities & HSM_CAP_SIGN_GOSSIP) != 0; + + case WIRE_HSMD_SIGN_DELAYED_PAYMENT_TO_US: + case WIRE_HSMD_SIGN_REMOTE_HTLC_TO_US: + case WIRE_HSMD_SIGN_PENALTY_TO_US: + case WIRE_HSMD_SIGN_LOCAL_HTLC_TX: + return (client->capabilities & HSM_CAP_SIGN_ONCHAIN_TX) != 0; + + case WIRE_HSMD_GET_PER_COMMITMENT_POINT: + case WIRE_HSMD_CHECK_FUTURE_SECRET: + case WIRE_HSMD_READY_CHANNEL: + return (client->capabilities & HSM_CAP_COMMITMENT_POINT) != 0; + + case WIRE_HSMD_SIGN_REMOTE_COMMITMENT_TX: + case WIRE_HSMD_SIGN_REMOTE_HTLC_TX: + case WIRE_HSMD_VALIDATE_COMMITMENT_TX: + case WIRE_HSMD_VALIDATE_REVOCATION: + return (client->capabilities & HSM_CAP_SIGN_REMOTE_TX) != 0; + + case WIRE_HSMD_SIGN_MUTUAL_CLOSE_TX: + return (client->capabilities & HSM_CAP_SIGN_CLOSING_TX) != 0; + + case WIRE_HSMD_SIGN_OPTION_WILL_FUND_OFFER: + return (client->capabilities & HSM_CAP_SIGN_WILL_FUND_OFFER) != 0; + + case WIRE_HSMD_INIT: + case WIRE_HSMD_NEW_CHANNEL: + case WIRE_HSMD_CLIENT_HSMFD: + case WIRE_HSMD_SIGN_WITHDRAWAL: + case WIRE_HSMD_SIGN_INVOICE: + case WIRE_HSMD_SIGN_COMMITMENT_TX: + case WIRE_HSMD_GET_CHANNEL_BASEPOINTS: + case WIRE_HSMD_DEV_MEMLEAK: + case WIRE_HSMD_SIGN_MESSAGE: + case WIRE_HSMD_GET_OUTPUT_SCRIPTPUBKEY: + case WIRE_HSMD_SIGN_BOLT12: + return (client->capabilities & HSM_CAP_MASTER) != 0; + + /*~ These are messages sent by the HSM so we should never receive them. */ + /* FIXME: Since we autogenerate these, we should really generate separate + * enums for replies to avoid this kind of clutter! */ + case WIRE_HSMD_ECDH_RESP: + case WIRE_HSMD_CANNOUNCEMENT_SIG_REPLY: + case WIRE_HSMD_CUPDATE_SIG_REPLY: + case WIRE_HSMD_CLIENT_HSMFD_REPLY: + case WIRE_HSMD_NEW_CHANNEL_REPLY: + case WIRE_HSMD_READY_CHANNEL_REPLY: + case WIRE_HSMD_NODE_ANNOUNCEMENT_SIG_REPLY: + case WIRE_HSMD_SIGN_WITHDRAWAL_REPLY: + case WIRE_HSMD_SIGN_INVOICE_REPLY: + case WIRE_HSMD_INIT_REPLY: + case WIRE_HSMSTATUS_CLIENT_BAD_REQUEST: + case WIRE_HSMD_SIGN_COMMITMENT_TX_REPLY: + case WIRE_HSMD_VALIDATE_COMMITMENT_TX_REPLY: + case WIRE_HSMD_VALIDATE_REVOCATION_REPLY: + case WIRE_HSMD_SIGN_TX_REPLY: + case WIRE_HSMD_SIGN_OPTION_WILL_FUND_OFFER_REPLY: + case WIRE_HSMD_GET_PER_COMMITMENT_POINT_REPLY: + case WIRE_HSMD_CHECK_FUTURE_SECRET_REPLY: + case WIRE_HSMD_GET_CHANNEL_BASEPOINTS_REPLY: + case WIRE_HSMD_DEV_MEMLEAK_REPLY: + case WIRE_HSMD_SIGN_MESSAGE_REPLY: + case WIRE_HSMD_GET_OUTPUT_SCRIPTPUBKEY_REPLY: + case WIRE_HSMD_SIGN_BOLT12_REPLY: + break; + } + return false; +} + +/*~ This is the core of the HSM daemon: handling requests. */ +static struct io_plan *handle_client(struct io_conn *conn, struct client *c) +{ + enum hsmd_wire t = fromwire_peektype(c->msg_in); + + if (!is_lightningd(c)) + status_peer_debug(&c->id, "Got %s", hsmd_wire_name(t)); + + STATUS_DEBUG("Client: Received message %d from client", t); + + /* Before we do anything else, is this client allowed to do + * what he asks for? */ + if (!check_client_capabilities(c, t)) + return bad_req_fmt(conn, c, c->msg_in, + "does not have capability to run %d", t); + + /* If we aren't initialized yet we better get an init message + * first. Otherwise we don't load the secret and every + * signature we produce is just going to be junk. */ + if (!initialized && t != WIRE_HSMD_INIT) + status_failed(STATUS_FAIL_MASTER_IO, + "hsmd was not initialized correctly, expected " + "message type %d, got %d", + WIRE_HSMD_INIT, t); + + /* Now actually go and do what the client asked for */ + switch (t) { + case WIRE_HSMD_INIT: + return init_hsm(conn, c, c->msg_in); + + case WIRE_HSMD_CLIENT_HSMFD: + return pass_client_hsmfd(conn, c, c->msg_in); + + case WIRE_HSMD_NEW_CHANNEL: + return handle_new_channel(conn, c, c->msg_in); + + case WIRE_HSMD_READY_CHANNEL: + return handle_ready_channel(conn, c, c->msg_in); + + case WIRE_HSMD_GET_CHANNEL_BASEPOINTS: + return handle_get_channel_basepoints(conn, c, c->msg_in); + + case WIRE_HSMD_GET_OUTPUT_SCRIPTPUBKEY: + return handle_get_output_scriptpubkey(conn, c, c->msg_in); + + case WIRE_HSMD_ECDH_REQ: + return handle_ecdh(conn, c, c->msg_in); + + case WIRE_HSMD_CANNOUNCEMENT_SIG_REQ: + return handle_cannouncement_sig(conn, c, c->msg_in); + + case WIRE_HSMD_CUPDATE_SIG_REQ: + return handle_channel_update_sig(conn, c, c->msg_in); + + case WIRE_HSMD_NODE_ANNOUNCEMENT_SIG_REQ: + return handle_sign_node_announcement(conn, c, c->msg_in); + + case WIRE_HSMD_SIGN_INVOICE: + return handle_sign_invoice(conn, c, c->msg_in); + + case WIRE_HSMD_SIGN_WITHDRAWAL: + return handle_sign_withdrawal_tx(conn, c, c->msg_in); + + case WIRE_HSMD_SIGN_COMMITMENT_TX: + return handle_sign_commitment_tx(conn, c, c->msg_in); + + case WIRE_HSMD_VALIDATE_COMMITMENT_TX: + return handle_validate_commitment_tx(conn, c, c->msg_in); + + case WIRE_HSMD_VALIDATE_REVOCATION: + return handle_validate_revocation(conn, c, c->msg_in); + + case WIRE_HSMD_SIGN_DELAYED_PAYMENT_TO_US: + return handle_sign_delayed_payment_to_us(conn, c, c->msg_in); + + case WIRE_HSMD_SIGN_REMOTE_HTLC_TO_US: + return handle_sign_remote_htlc_to_us(conn, c, c->msg_in); + + case WIRE_HSMD_SIGN_PENALTY_TO_US: + return handle_sign_penalty_to_us(conn, c, c->msg_in); + + case WIRE_HSMD_SIGN_LOCAL_HTLC_TX: + return handle_sign_local_htlc_tx(conn, c, c->msg_in); + + case WIRE_HSMD_GET_PER_COMMITMENT_POINT: + return handle_get_per_commitment_point(conn, c, c->msg_in); + + case WIRE_HSMD_CHECK_FUTURE_SECRET: + return handle_check_future_secret(conn, c, c->msg_in); + + case WIRE_HSMD_SIGN_REMOTE_COMMITMENT_TX: + return handle_sign_remote_commitment_tx(conn, c, c->msg_in); + + case WIRE_HSMD_SIGN_REMOTE_HTLC_TX: + return handle_sign_remote_htlc_tx(conn, c, c->msg_in); + + case WIRE_HSMD_SIGN_MUTUAL_CLOSE_TX: + return handle_sign_mutual_close_tx(conn, c, c->msg_in); + + case WIRE_HSMD_SIGN_MESSAGE: + return handle_sign_message(conn, c, c->msg_in); + + case WIRE_HSMD_SIGN_OPTION_WILL_FUND_OFFER: + return handle_sign_option_will_fund_offer(conn, c, c->msg_in); + + case WIRE_HSMD_SIGN_BOLT12: + return handle_sign_bolt12(conn, c, c->msg_in); +#if DEVELOPER + case WIRE_HSMD_DEV_MEMLEAK: + return handle_memleak(conn, c, c->msg_in); +#else + case WIRE_HSMD_DEV_MEMLEAK: +#endif /* DEVELOPER */ + case WIRE_HSMD_ECDH_RESP: + case WIRE_HSMD_CANNOUNCEMENT_SIG_REPLY: + case WIRE_HSMD_CUPDATE_SIG_REPLY: + case WIRE_HSMD_CLIENT_HSMFD_REPLY: + case WIRE_HSMD_NEW_CHANNEL_REPLY: + case WIRE_HSMD_READY_CHANNEL_REPLY: + case WIRE_HSMD_NODE_ANNOUNCEMENT_SIG_REPLY: + case WIRE_HSMD_SIGN_WITHDRAWAL_REPLY: + case WIRE_HSMD_SIGN_INVOICE_REPLY: + case WIRE_HSMD_INIT_REPLY: + case WIRE_HSMSTATUS_CLIENT_BAD_REQUEST: + case WIRE_HSMD_SIGN_COMMITMENT_TX_REPLY: + case WIRE_HSMD_VALIDATE_COMMITMENT_TX_REPLY: + case WIRE_HSMD_VALIDATE_REVOCATION_REPLY: + case WIRE_HSMD_SIGN_TX_REPLY: + case WIRE_HSMD_SIGN_OPTION_WILL_FUND_OFFER_REPLY: + case WIRE_HSMD_GET_PER_COMMITMENT_POINT_REPLY: + case WIRE_HSMD_CHECK_FUTURE_SECRET_REPLY: + case WIRE_HSMD_GET_CHANNEL_BASEPOINTS_REPLY: + case WIRE_HSMD_DEV_MEMLEAK_REPLY: + case WIRE_HSMD_SIGN_MESSAGE_REPLY: + case WIRE_HSMD_GET_OUTPUT_SCRIPTPUBKEY_REPLY: + case WIRE_HSMD_SIGN_BOLT12_REPLY: + break; + } + + return bad_req_fmt(conn, c, c->msg_in, "Unknown request"); +} + +static void master_gone(struct io_conn *unused UNUSED, struct client *c UNUSED) +{ + daemon_shutdown(); + /* Can't tell master, it's gone. */ + exit(2); +} + +int main(int argc, char *argv[]) +{ + struct client *master; + + setup_locale(); + + /* This sets up tmpctx, various DEVELOPER options, backtraces, etc. */ + subdaemon_setup(argc, argv); + + /* A trivial daemon_conn just for writing. */ + status_conn = daemon_conn_new(NULL, STDIN_FILENO, NULL, NULL, NULL); + status_setup_async(status_conn); + uintmap_init(&clients); + + master = new_client(NULL, NULL, NULL, 0, + HSM_CAP_MASTER | HSM_CAP_SIGN_GOSSIP | HSM_CAP_ECDH, + REQ_FD); + + /* First client == lightningd. */ + assert(is_lightningd(master)); + + /* When conn closes, everything is freed. */ + io_set_finish(master->conn, master_gone, master); + + /* Setup the remote proxy */ + proxy_setup(); + + /*~ The two NULL args are a list of timers, and the timer which expired: + * we don't have any timers. */ + io_loop(NULL, NULL); + + /*~ This should never be reached: io_loop only exits on io_break which + * we don't call, a timer expiry which we don't have, or all connections + * being closed, and closing the master calls master_gone. */ + abort(); +} + +/*~ Congratulations on making it through the first of the seven dwarves! + * (And Christian wondered why I'm so fond of having separate daemons!). + * + * We continue our story in the next-more-complex daemon: connectd/connectd.c + */ diff --git a/contrib/remote_hsmd/proxy.cc b/contrib/remote_hsmd/proxy.cc new file mode 100644 index 000000000000..4d0b928e3bba --- /dev/null +++ b/contrib/remote_hsmd/proxy.cc @@ -0,0 +1,1681 @@ +/* This needs to be first */ +#define __STDC_FORMAT_MACROS + +#include "contrib/remote_hsmd/dump.hpp" +#include "contrib/remote_hsmd/proxy.hpp" +#include "contrib/remote_hsmd/remotesigner.grpc.pb.h" +#include "contrib/remote_hsmd/remotesigner.pb.h" +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +} +#include +#include +#include +extern "C" { +#include +} +#include +#include /* These two only needed for sleep() and getpid() */ +#include +extern "C" { +#include +#include +} + + +using std::cerr; +using std::endl; +using std::ostringstream; +using std::string; +using std::unique_ptr; + +using grpc::Channel; +using grpc::ClientContext; +using grpc::Status; +using grpc::StatusCode; + +using ::google::protobuf::RepeatedPtrField; + +using namespace remotesigner; + +namespace { +unique_ptr stub; +string last_message; +struct node_id self_id; + +proxy_stat map_status(Status const & status) +{ + StatusCode code = status.error_code(); + + // FIXME - this is bogus, but the pytest framework loses our + // status_unusual messages. + if (code != StatusCode::OK) { + cerr << "PID: " << getpid() << ' ' + << "PROXY-HSMD grpc::StatusCode " << int(code) + << ": " << status.error_message() + << endl; + } + switch (code) { + case StatusCode::OK: return PROXY_OK; + case StatusCode::CANCELLED: return PROXY_CANCELLED; + case StatusCode::DEADLINE_EXCEEDED: return PROXY_TIMEOUT; + case StatusCode::UNAVAILABLE: return PROXY_UNAVAILABLE; + case StatusCode::INVALID_ARGUMENT: return PROXY_INVALID_ARGUMENT; + case StatusCode::INTERNAL: return PROXY_INTERNAL_ERROR; + default: + cerr << "UNHANDLED grpc::StatusCode " << int(code) + << ": " << status.error_message() + << endl; + abort(); + } +} + +/* BIP144: + * If the witness is empty, the old serialization format should be used. */ +bool uses_witness(const struct wally_tx *wtx) +{ + size_t i; + for (i = 0; i < wtx->num_inputs; i++) { + if (wtx->inputs[i].witness) + return true; + } + return false; +} + + +string serialized_wtx(struct wally_tx const *wtx, bool bip144) +{ + int res; + size_t len, written; + u8 *serialized;; + u8 flag = 0; + + if (bip144 && uses_witness(wtx)) + flag |= WALLY_TX_FLAG_USE_WITNESS; + + res = wally_tx_get_length(wtx, flag, &len); + assert(res == WALLY_OK); + + string retval(len, '\0'); + res = wally_tx_to_bytes(wtx, flag, (unsigned char *)&retval[0], + retval.size(), &written); + assert(res == WALLY_OK); + assert(len == written); + return retval; +} + +void marshal_channel_nonce(struct node_id const *peer_id, u64 dbid, + ChannelNonce *o_np) +{ + o_np->set_data(string((char const *)peer_id->k, sizeof(peer_id->k)) + + string((char const *)&dbid, sizeof(dbid))); +} + +void marshal_secret(struct secret const *ss, Secret *o_sp) +{ + o_sp->set_data(ss->data, sizeof(ss->data)); +} + +void marshal_bip32seed(struct secret const *ss, BIP32Seed *o_sp) +{ + o_sp->set_data(ss->data, sizeof(ss->data)); +} + +void marshal_node_id(struct node_id const *np, NodeId *o_np) +{ + o_np->set_data(np->k, sizeof(np->k)); +} + +void marshal_pubkey(struct pubkey const *pp, PubKey *o_pp) +{ + u8 pubkey_der[PUBKEY_CMPR_LEN]; + pubkey_to_der(pubkey_der, pp); + + o_pp->set_data(pubkey_der, sizeof(pubkey_der)); +} + +void marshal_utxo(struct utxo const *up, InputDescriptor *idesc) +{ + idesc->mutable_key_loc()->add_key_path(up->keyindex); + idesc->set_value_sat(up->amount.satoshis); + idesc->set_spend_type(up->is_p2sh + ? SpendType::P2SH_P2WPKH + : SpendType::P2WPKH); + if (up->close_info) { + UnilateralCloseInfo *cinfo = + idesc->mutable_key_loc()->mutable_close_info(); + marshal_channel_nonce(&up->close_info->peer_id, + up->close_info->channel_id, + cinfo->mutable_channel_nonce()); + if (up->close_info->commitment_point) + marshal_pubkey(up->close_info->commitment_point, + cinfo->mutable_commitment_point()); + } +} + +void marshal_outpoint(struct bitcoin_txid const *txid, u16 txout, Outpoint *o_op) +{ + o_op->set_txid(txid->shad.sha.u.u8, sizeof(txid->shad.sha.u.u8)); + o_op->set_index(txout); +} + +void marshal_script(u8 const *script, string *o_script) +{ + if (script) + o_script->assign((char const *)script, tal_count(script)); +} + +void marshal_bitcoin_signature(struct bitcoin_signature const *sp, BitcoinSignature *o_sig) +{ + u8 der[73]; + size_t len = signature_to_der(der, sp); + o_sig->set_data(der, len); +} + +void marshal_basepoints(struct basepoints const *bps, + struct pubkey *funding_pubkey, + Basepoints * o_bps) +{ + marshal_pubkey(&bps->revocation, o_bps->mutable_revocation()); + marshal_pubkey(&bps->payment, o_bps->mutable_payment()); + marshal_pubkey(&bps->htlc, o_bps->mutable_htlc()); + marshal_pubkey(&bps->delayed_payment, o_bps->mutable_delayed_payment()); + marshal_pubkey(funding_pubkey, o_bps->mutable_funding_pubkey()); +} + +void marshal_single_input_tx(struct bitcoin_tx const *tx, + u8 const *redeem_script, + Transaction *o_tp) +{ + assert(tx->psbt->num_outputs == tx->wtx->num_outputs); + + o_tp->set_raw_tx_bytes(serialized_wtx(tx->wtx, true)); + + assert(tx->wtx->num_inputs == 1); + assert(tx->psbt->num_inputs == 1); + InputDescriptor *idesc = o_tp->add_input_descs(); + idesc->set_value_sat(psbt_input_get_amount(tx->psbt, 0).satoshis); + if (redeem_script) + idesc->set_redeem_script((const char *) redeem_script, + tal_count(redeem_script)); + + for (size_t ii = 0; ii < tx->wtx->num_outputs; ii++) { + OutputDescriptor *odesc = o_tp->add_output_descs(); + + // Add witness script + if (tx->psbt->outputs[ii].witness_script_len) + odesc->set_witscript( + (const char *) + tx->psbt->outputs[ii].witness_script, + tx->psbt->outputs[ii].witness_script_len); + + // Add keypath + struct wally_map *mp = &tx->psbt->outputs[ii].keypaths; + if (mp->num_items == 1) { + const struct wally_map_item *ip = &mp->items[0]; + size_t npath = + (ip->value_len - BIP32_KEY_FINGERPRINT_LEN) / sizeof(uint32_t); + uint32_t *path = (uint32_t *) (ip->value + BIP32_KEY_FINGERPRINT_LEN); + for (size_t jj = 0; jj < npath; ++jj) { + odesc->mutable_key_loc()->add_key_path(le32_to_cpu(path[jj])); + } + } + + } +} + +void marshal_rhashes(const struct sha256 *rhashes, + RepeatedPtrField *payment_hashes) +{ + for (size_t ii = 0; ii < tal_count(rhashes); ++ii) { + payment_hashes->Add(string((const char *) &rhashes[ii], + sizeof(struct sha256))); + } +} + +void marshal_htlc(const struct simple_htlc *htlc, HTLCInfo *o_htlc) +{ + o_htlc->set_value_sat(htlc->amount.millisatoshis / 1000); + o_htlc->set_payment_hash(&htlc->payment_hash, sizeof(htlc->payment_hash)); + o_htlc->set_cltv_expiry(htlc->cltv_expiry); +} + +void unmarshal_secret(Secret const &ss, struct secret *o_sp) +{ + assert(ss.data().size() == sizeof(o_sp->data)); + memcpy(o_sp->data, ss.data().data(), sizeof(o_sp->data)); + +} +void unmarshal_node_id(NodeId const &nn, struct node_id *o_np) +{ + assert(nn.data().size() == sizeof(o_np->k)); + memcpy(o_np->k, nn.data().data(), sizeof(o_np->k)); +} + +void unmarshal_pubkey(PubKey const &pk, struct pubkey *o_pp) +{ + bool ok = pubkey_from_der((u8 const *)pk.data().data(), + pk.data().size(), + o_pp); + assert(ok); +} + +void unmarshal_ext_pubkey(ExtPubKey const &xpk, struct ext_key *o_xp) +{ + int rv = bip32_key_from_base58(xpk.encoded().data(), o_xp); + assert(rv == WALLY_OK); +} + +void unmarshal_bitcoin_signature(BitcoinSignature const &bs, + struct bitcoin_signature *o_sig) +{ + bool ok = signature_from_der( + (const u8*)bs.data().data(), + bs.data().size(), + o_sig); + assert(ok); +} + +void unmarshal_ecdsa_signature(ECDSASignature const &es, + secp256k1_ecdsa_signature *o_sig) +{ + int ok = secp256k1_ecdsa_signature_parse_der( + secp256k1_ctx, + o_sig, + (const u8*)es.data().data(), + es.data().size()); + assert(ok); +} + +void unmarshal_ecdsa_recoverable_signature(ECDSARecoverableSignature const &es, + secp256k1_ecdsa_recoverable_signature *o_sig) +{ + assert(es.data().size() == 65); + int recid = es.data().data()[64]; + int ok = secp256k1_ecdsa_recoverable_signature_parse_compact( + secp256k1_ctx, + o_sig, + (const u8*)es.data().data(), + recid); + assert(ok); +} + +void unmarshal_witnesses(RepeatedPtrField const &wits, u8 ****o_wits) +{ + u8 ***owits = NULL; + int nwits = wits.size(); + if (nwits > 0) { + owits = tal_arrz(tmpctx, u8**, nwits); + for (size_t ii = 0; ii < nwits; ++ii) { + owits[ii] = tal_arrz(owits, u8*, 2); + Witness const &wit = wits.Get(ii); + const string &sig = wit.signature().data(); + const string &pubkey = wit.pubkey().data(); + owits[ii][0] = tal_arr(owits[ii], u8, sig.size()); + memcpy(owits[ii][0], sig.data(), sig.size()); + owits[ii][1] = tal_arr(owits[ii], u8, pubkey.size()); + memcpy(owits[ii][1], pubkey.data(), pubkey.size()); + } + } + *o_wits = owits; +} + +/* Copied from ccan/mem/mem.h which the c++ compiler doesn't like */ +static inline bool memeq(const void *a, size_t al, const void *b, size_t bl) +{ + return al == bl && !memcmp(a, b, bl); +} + +} /* end namespace */ + +extern "C" { +const char *proxy_last_message(void) +{ + return last_message.c_str(); +} + +void proxy_setup() +{ + STATUS_DEBUG("%s:%d %s pid:%d", __FILE__, __LINE__, __FUNCTION__, getpid()); + auto channel = grpc::CreateChannel("localhost:50051", + grpc::InsecureChannelCredentials()); + stub = Signer::NewStub(channel); + last_message = ""; +} + +void proxy_set_node_id(const struct node_id *node_id) +{ + self_id = *node_id; +} + +proxy_stat proxy_init_hsm(struct bip32_key_version *bip32_key_version, + struct chainparams const *chainparams, + bool coldstart, + struct secret *hsm_secret, + struct node_id *o_node_id) +{ + STATUS_DEBUG( + "%s:%d %s { \"network\":%s, \"hsm_secret\":%s, \"coldstart\":%s }", + __FILE__, __LINE__, __FUNCTION__, + chainparams->network_name, + hsm_secret != NULL ? dump_secret(hsm_secret).c_str() : "\"\"", + coldstart ? "true" : "false" + ); + + last_message = ""; + InitRequest req; + + auto nc = req.mutable_node_config(); + nc->set_key_derivation_style(NodeConfig::NATIVE); + + auto cp = req.mutable_chainparams(); + cp->set_network_name(chainparams->network_name); + + req.set_coldstart(coldstart); + + // If we are running integration tests the secret will be forced. + if (hsm_secret != NULL) + marshal_bip32seed(hsm_secret, req.mutable_hsm_secret()); + + ClientContext context; + InitReply rsp; + Status status = stub->Init(&context, req, &rsp); + if (status.ok()) { + unmarshal_node_id(rsp.node_id(), o_node_id); + unmarshal_node_id(rsp.node_id(), &self_id); + STATUS_DEBUG("%s:%d %s { \"node_id\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(o_node_id).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: %s", + __FILE__, __LINE__, __FUNCTION__, + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_get_ext_pub_key(struct ext_key *o_ext_pubkey) +{ + // TODO + STATUS_DEBUG("%s:%d %s", __FILE__, __LINE__, __FUNCTION__); + + last_message = ""; + GetExtPubKeyRequest req; + + marshal_node_id(&self_id, req.mutable_node_id()); + + ClientContext context; + GetExtPubKeyReply rsp; + Status status = stub->GetExtPubKey(&context, req, &rsp); + if (status.ok()) { + unmarshal_ext_pubkey(rsp.xpub(), o_ext_pubkey); + STATUS_DEBUG("%s:%d %s " + "{ \"ext_pubkey\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_ext_pubkey(o_ext_pubkey).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: %s", + __FILE__, __LINE__, __FUNCTION__, + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_ecdh(const struct pubkey *point, + struct secret *o_ss) +{ + STATUS_DEBUG( + "%s:%d %s { \"self_id\":%s, \"point\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_pubkey(point).c_str() + ); + + last_message = ""; + ECDHRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + marshal_pubkey(point, req.mutable_point()); + + ClientContext context; + ECDHReply rsp; + Status status = stub->ECDH(&context, req, &rsp); + if (status.ok()) { + unmarshal_secret(rsp.shared_secret(), o_ss); + STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"ss\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_secret(o_ss).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_pass_client_hsmfd( + struct node_id *peer_id, + u64 dbid, + u64 capabilities) +{ + STATUS_DEBUG( + "%s:%d %s " + "{ \"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " + "\"capabilities\":%" PRIu64 " }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_node_id(peer_id).c_str(), + dbid, + capabilities + ); + +/* We used to synthesize NewChannel here, but now have an explicit + * interface. This whole method can go away. */ +#if 0 + last_message = ""; + NewChannelRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); + + ClientContext context; + NewChannelReply rsp; + Status status = stub->NewChannel(&context, req, &rsp); + if (status.ok()) { + STATUS_DEBUG("%s:%d %s { \"self_id\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +#else + last_message = "success"; + return PROXY_OK; +#endif +} + +proxy_stat proxy_handle_new_channel( + struct node_id *peer_id, + u64 dbid) +{ + STATUS_DEBUG( + "%s:%d %s " + "{ \"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 " }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_node_id(peer_id).c_str(), + dbid); + + last_message = ""; + NewChannelRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce0()); + + ClientContext context; + NewChannelReply rsp; + Status status = stub->NewChannel(&context, req, &rsp); + if (status.ok()) { + STATUS_DEBUG("%s:%d %s { \"self_id\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_ready_channel( + struct node_id *peer_id, + u64 dbid, + bool is_outbound, + struct amount_sat *channel_value, + struct amount_msat *push_value, + struct bitcoin_txid *funding_txid, + u16 funding_txout, + u16 holder_to_self_delay, + u8 *holder_shutdown_script, + u32 *holder_shutdown_wallet_index, + struct basepoints *counterparty_basepoints, + struct pubkey *counterparty_funding_pubkey, + u16 counterparty_to_self_delay, + u8 *counterparty_shutdown_script, + struct channel_type *channel_type) +{ + bool option_static_remotekey = channel_type_has(channel_type, OPT_STATIC_REMOTEKEY); + bool option_anchor_outputs = channel_type_has(channel_type, OPT_ANCHOR_OUTPUTS); + + STATUS_DEBUG( + "%s:%d %s { " + "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " + "\"is_outbound\":%s, \"channel_value\":%" PRIu64 ", " + "\"push_value\":%" PRIu64 ", " + "\"funding_txid\":%s, \"funding_txout\":%d, " + "\"holder_to_self_delay\":%d, " + "\"holder_shutdown_script\":%s, " + "\"holder_shutdown_wallet_index\":%s, " + "\"counterparty_basepoints\":%s, " + "\"counterparty_funding_pubkey\":%s, " + "\"counterparty_to_self_delay\":%d, " + "\"counterparty_shutdown_script\":%s, " + "\"option_static_remotekey\":%s, " + "\"option_anchor_outputs\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_node_id(peer_id).c_str(), + dbid, + (is_outbound ? "true" : "false"), + channel_value->satoshis, + push_value->millisatoshis, + dump_bitcoin_txid(funding_txid).c_str(), + funding_txout, + holder_to_self_delay, + dump_hex(holder_shutdown_script, + tal_count(holder_shutdown_script)).c_str(), + dump_optional_wallet_index(holder_shutdown_wallet_index).c_str(), + dump_basepoints(counterparty_basepoints).c_str(), + dump_pubkey(counterparty_funding_pubkey).c_str(), + counterparty_to_self_delay, + dump_hex(counterparty_shutdown_script, + tal_count(counterparty_shutdown_script)).c_str(), + (option_static_remotekey ? "true" : "false"), + (option_anchor_outputs ? "true" : "false") + ); + + last_message = ""; + ReadyChannelRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce0()); + req.set_is_outbound(is_outbound); + req.set_channel_value_sat(channel_value->satoshis); + req.set_push_value_msat(push_value->millisatoshis); + marshal_outpoint(funding_txid, + funding_txout, req.mutable_funding_outpoint()); + req.set_holder_selected_contest_delay(holder_to_self_delay); + marshal_script(holder_shutdown_script, + req.mutable_holder_shutdown_script()); + if (holder_shutdown_wallet_index != NULL) + req.add_holder_shutdown_key_path(*holder_shutdown_wallet_index); + marshal_basepoints(counterparty_basepoints, counterparty_funding_pubkey, + req.mutable_counterparty_basepoints()); + req.set_counterparty_selected_contest_delay(counterparty_to_self_delay); + marshal_script(counterparty_shutdown_script, + req.mutable_counterparty_shutdown_script()); + if (option_anchor_outputs) + req.set_commitment_type(ReadyChannelRequest_CommitmentType_ANCHORS); + else if (option_static_remotekey) + req.set_commitment_type(ReadyChannelRequest_CommitmentType_STATIC_REMOTEKEY); + else + req.set_commitment_type(ReadyChannelRequest_CommitmentType_LEGACY); + + ClientContext context; + ReadyChannelReply rsp; + Status status = stub->ReadyChannel(&context, req, &rsp); + if (status.ok()) { + STATUS_DEBUG("%s:%d %s { \"self_id\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_sign_withdrawal_tx( + struct bitcoin_tx_output **outputs, + struct utxo **utxos, + struct wally_psbt *psbt, + u8 ****o_wits) +{ + STATUS_DEBUG( + "%s:%d %s { " + "\"self_id\":%s, " + "\"utxos\":%s, \"outputs\":%s, \"psbt\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_utxos((const struct utxo **)utxos).c_str(), + dump_bitcoin_tx_outputs( + (const struct bitcoin_tx_output **)outputs).c_str(), + dump_wally_psbt(psbt).c_str() + ); + + last_message = ""; + + // This code is mimicking psbt_txid at bitcoin/psbt.c:796: + // + /* You can *almost* take txid of global tx. But @niftynei thought + * about this far more than me and pointed out that P2SH + * inputs would not be represented, so here we go. */ + struct wally_tx *tx; + tal_wally_start(); + wally_tx_clone_alloc(psbt->tx, 0, &tx); + for (size_t i = 0; i < tx->num_inputs; i++) { + if (psbt->inputs[i].final_scriptsig) { + wally_tx_set_input_script(tx, i, + psbt->inputs[i].final_scriptsig, + psbt->inputs[i].final_scriptsig_len); + } else if (psbt->inputs[i].redeem_script) { + u8 *script; + + /* P2SH requires push of the redeemscript, from libwally src */ + script = tal_arr(tmpctx, u8, 0); + script_push_bytes(&script, + psbt->inputs[i].redeem_script, + psbt->inputs[i].redeem_script_len); + wally_tx_set_input_script(tx, i, script, tal_bytelen(script)); + } + } + tal_wally_end(tal_steal(psbt, tx)); + + + SignOnchainTxRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + + // Serialize the tx we modified above which includes witscripts. + req.mutable_tx()->set_raw_tx_bytes(serialized_wtx(tx, true)); + + assert(psbt->tx->num_inputs >= tal_count(utxos)); + size_t uu = 0; + for (size_t ii = 0; ii < psbt->tx->num_inputs; ++ii) { + InputDescriptor *idesc = req.mutable_tx()->add_input_descs(); + if (uu < tal_count(utxos) && + wally_tx_input_spends(&psbt->tx->inputs[ii], + &utxos[uu]->outpoint)) { + marshal_utxo(utxos[uu], idesc); + ++uu; + } + } + assert(uu == tal_count(utxos)); + + for (size_t ii = 0; ii < psbt->tx->num_outputs; ++ii) { + OutputDescriptor *odesc = req.mutable_tx()->add_output_descs(); + struct wally_map *mp = &psbt->outputs[ii].keypaths; + if (mp->num_items == 1) { + const struct wally_map_item *ip = &mp->items[0]; + size_t npath = + (ip->value_len - BIP32_KEY_FINGERPRINT_LEN) / sizeof(uint32_t); + uint32_t *path = (uint32_t *) (ip->value + BIP32_KEY_FINGERPRINT_LEN); + for (size_t jj = 0; jj < npath; ++jj) { + odesc->mutable_key_loc()->add_key_path(le32_to_cpu(path[jj])); + } + } + } + + ClientContext context; + SignOnchainTxReply rsp; + Status status = stub->SignOnchainTx(&context, req, &rsp); + if (status.ok()) { + unmarshal_witnesses(rsp.witnesses(), o_wits); + STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"witnesses\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_witnesses((u8 const ***) *o_wits).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_sign_remote_commitment_tx( + struct bitcoin_tx *tx, + const struct pubkey *counterparty_funding_pubkey, + struct node_id *peer_id, + u64 dbid, + const struct pubkey *remote_per_commit, + struct simple_htlc **htlcs, + u64 commit_num, u32 feerate, + struct bitcoin_signature *o_sig) +{ + STATUS_DEBUG( + "%s:%d %s { " + "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " + "\"counterparty_funding_pubkey\":%s, " + "\"remote_per_commit\":%s, \"tx\":%s, " + "\"htlcs\":%s, " + "\"commit_num\":%" PRIu64 ", " + "\"feerate\":%d }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_node_id(peer_id).c_str(), + dbid, + dump_pubkey(counterparty_funding_pubkey).c_str(), + dump_pubkey(remote_per_commit).c_str(), + dump_tx(tx).c_str(), + dump_htlcs((const struct simple_htlc **) htlcs, tal_count(htlcs)).c_str(), + commit_num, feerate + ); + + last_message = ""; + SignCounterpartyCommitmentTxRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); + marshal_pubkey(remote_per_commit, + req.mutable_remote_per_commit_point()); + marshal_single_input_tx(tx, NULL, req.mutable_tx()); + for (size_t ii = 0; ii < tal_count(htlcs); ++ii) { + if (htlcs[ii]->side == REMOTE) { + marshal_htlc(htlcs[ii], req.add_offered_htlcs()); + } else { + marshal_htlc(htlcs[ii], req.add_received_htlcs()); + } + } + req.set_commit_num(commit_num); + req.set_feerate_sat_per_kw(feerate); + + ClientContext context; + SignatureReply rsp; + Status status = stub->SignCounterpartyCommitmentTx(&context, req, &rsp); + if (status.ok()) { + unmarshal_bitcoin_signature(rsp.signature(), o_sig); + STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_bitcoin_signature(o_sig).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_get_per_commitment_point( + struct node_id *peer_id, + u64 dbid, + u64 n, + struct pubkey *o_per_commitment_point, + struct secret **o_old_secret) +{ + STATUS_DEBUG("%s:%d %s { " + "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " + "\"n\":%" PRIu64 " }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_node_id(peer_id).c_str(), + dbid, + n + ); + + last_message = ""; + GetPerCommitmentPointRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); + req.set_n(n); + + ClientContext context; + GetPerCommitmentPointReply rsp; + Status status = stub->GetPerCommitmentPoint(&context, req, &rsp); + if (status.ok()) { + unmarshal_pubkey(rsp.per_commitment_point(), + o_per_commitment_point); + if (rsp.old_secret().data().empty()) { + *o_old_secret = NULL; + } else { + *o_old_secret = tal_arr(tmpctx, struct secret, 1); + unmarshal_secret(rsp.old_secret(), *o_old_secret); + } + STATUS_DEBUG("%s:%d %s { " + "\"self_id\":%s, " + "\"per_commitment_point\":%s, " + "\"old_secret\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_pubkey(o_per_commitment_point).c_str(), + (*o_old_secret ? + dump_secret(*o_old_secret).c_str() : "")); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_sign_invoice( + u5 *u5bytes, + u8 *hrpu8, + secp256k1_ecdsa_recoverable_signature *o_sig) +{ + STATUS_DEBUG("%s:%d %s { " + "\"self_id\":%s, \"u5bytes\":%s \"hrpu8\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_hex(u5bytes, tal_count(u5bytes)).c_str(), + string((const char *)hrpu8, tal_count(hrpu8)).c_str() + ); + + last_message = ""; + SignInvoiceRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + req.set_data_part(u5bytes, tal_count(u5bytes)); + req.set_human_readable_part((const char *)hrpu8, tal_count(hrpu8)); + + ClientContext context; + RecoverableNodeSignatureReply rsp; + Status status = stub->SignInvoice(&context, req, &rsp); + if (status.ok()) { + unmarshal_ecdsa_recoverable_signature(rsp.signature(), o_sig); + STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_secp256k1_ecdsa_recoverable_signature( + o_sig).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_sign_message( + u8 *msg, + secp256k1_ecdsa_recoverable_signature *o_sig) +{ + STATUS_DEBUG( + "%s:%d %s { \"self_id\":%s, \"msg\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_hex(msg, tal_count(msg)).c_str() + ); + + last_message = ""; + SignMessageRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + req.set_message(msg, tal_count(msg)); + + ClientContext context; + RecoverableNodeSignatureReply rsp; + Status status = stub->SignMessage(&context, req, &rsp); + if (status.ok()) { + unmarshal_ecdsa_recoverable_signature(rsp.signature(), o_sig); + STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_secp256k1_ecdsa_recoverable_signature( + o_sig).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_channel_update_sig( + u8 *channel_update, + secp256k1_ecdsa_signature *o_sig) +{ + STATUS_DEBUG("%s:%d %s { " + "\"self_id\":%s, \"channel_update\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_hex(channel_update, tal_count(channel_update)).c_str()); + + /* Skip the portion of the channel_update that we don't sign */ + size_t offset = 2 + 64; /* sizeof(type) + sizeof(signature) */ + size_t annsz = tal_count(channel_update); + + last_message = ""; + SignChannelUpdateRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + req.set_channel_update(channel_update + offset, annsz - offset); + + ClientContext context; + NodeSignatureReply rsp; + Status status = stub->SignChannelUpdate(&context, req, &rsp); + if (status.ok()) { + unmarshal_ecdsa_signature(rsp.signature(), o_sig); + STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_secp256k1_ecdsa_signature(o_sig).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_get_channel_basepoints( + struct node_id *peer_id, + u64 dbid, + struct basepoints *o_basepoints, + struct pubkey *o_funding_pubkey) +{ + STATUS_DEBUG("%s:%d %s { " + "\"self_id\":%s, \"peer_id\":%s \"dbid\":%" PRIu64 " }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_node_id(peer_id).c_str(), + dbid + ); + + last_message = ""; + GetChannelBasepointsRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); + + ClientContext context; + GetChannelBasepointsReply rsp; + Status status = stub->GetChannelBasepoints(&context, req, &rsp); + if (status.ok()) { + Basepoints const & bps = rsp.basepoints(); + unmarshal_pubkey(bps.revocation(), &o_basepoints->revocation); + unmarshal_pubkey(bps.payment(), &o_basepoints->payment); + unmarshal_pubkey(bps.htlc(), &o_basepoints->htlc); + unmarshal_pubkey(bps.delayed_payment(), + &o_basepoints->delayed_payment); + unmarshal_pubkey(bps.funding_pubkey(), o_funding_pubkey); + STATUS_DEBUG("%s:%d %s { " + "\"self_id\":%s, \"basepoints\":%s, " + "\"pubkey\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_basepoints(o_basepoints).c_str(), + dump_pubkey(o_funding_pubkey).c_str()); + + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_sign_mutual_close_tx( + struct bitcoin_tx *tx, + const struct pubkey *counterparty_funding_pubkey, + struct node_id *peer_id, + u64 dbid, + struct bitcoin_signature *o_sig) +{ + STATUS_DEBUG( + "%s:%d %s { " + "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " + "\"counterparty_funding_pubkey\":%s, \"tx\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_node_id(peer_id).c_str(), + dbid, + dump_pubkey(counterparty_funding_pubkey).c_str(), + dump_tx(tx).c_str() + ); + + last_message = ""; + SignMutualCloseTxRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); + marshal_single_input_tx(tx, NULL, req.mutable_tx()); + + ClientContext context; + SignatureReply rsp; + Status status = stub->SignMutualCloseTx(&context, req, &rsp); + if (status.ok()) { + unmarshal_bitcoin_signature(rsp.signature(), o_sig); + STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_bitcoin_signature(o_sig).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_sign_commitment_tx( + struct node_id *peer_id, + u64 dbid, + u64 commit_num, + struct bitcoin_signature *o_sig) +{ + STATUS_DEBUG( + "%s:%d %s { " + "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " + "\"commit_num\":%" PRIu64 " }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_node_id(peer_id).c_str(), + dbid, + commit_num + ); + + last_message = ""; + SignHolderCommitmentTxPhase2Request req; + marshal_node_id(&self_id, req.mutable_node_id()); + marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); + req.set_commit_num(commit_num); + + ClientContext context; + CommitmentTxSignatureReply rsp; + Status status = stub->SignHolderCommitmentTxPhase2(&context, req, &rsp); + if (status.ok()) { + unmarshal_bitcoin_signature(rsp.signature(), o_sig); + // NOTE - ignoring rsp.htlc_signatures + STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_bitcoin_signature(o_sig).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_validate_commitment_tx( + struct bitcoin_tx *tx, + struct node_id *peer_id, + u64 dbid, + struct simple_htlc **htlcs, + u64 commit_num, u32 feerate, + struct bitcoin_signature *commit_sig, + struct bitcoin_signature *htlc_sigs, + struct secret **o_old_secret, + struct pubkey *o_next_per_commitment_point) +{ + STATUS_DEBUG( + "%s:%d %s { " + "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " + "\"tx\":%s, " + "\"htlcs\":%s, " + "\"commit_num\":%" PRIu64 ", " + "\"feerate\":%d, " + "\"commit_sig\":%s, \"htlc_sigs\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_node_id(peer_id).c_str(), + dbid, + dump_tx(tx).c_str(), + dump_htlcs((const struct simple_htlc **) htlcs, tal_count(htlcs)).c_str(), + commit_num, feerate, + dump_bitcoin_signature(commit_sig).c_str(), + dump_htlc_signatures(htlc_sigs).c_str() + ); + + last_message = ""; + ValidateHolderCommitmentTxRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); + marshal_single_input_tx(tx, NULL, req.mutable_tx()); + for (size_t ii = 0; ii < tal_count(htlcs); ++ii) { + if (htlcs[ii]->side == LOCAL) { + marshal_htlc(htlcs[ii], req.add_offered_htlcs()); + } else { + marshal_htlc(htlcs[ii], req.add_received_htlcs()); + } + } + req.set_commit_num(commit_num); + req.set_feerate_sat_per_kw(feerate); + marshal_bitcoin_signature(commit_sig, req.mutable_commit_signature()); + for (size_t ii = 0; ii < tal_count(htlc_sigs); ++ii) { + marshal_bitcoin_signature(&htlc_sigs[ii], req.add_htlc_signatures()); + } + + ClientContext context; + ValidateHolderCommitmentTxReply rsp; + Status status = stub->ValidateHolderCommitmentTx(&context, req, &rsp); + if (status.ok()) { + unmarshal_pubkey(rsp.next_per_commitment_point(), o_next_per_commitment_point); + if (rsp.old_secret().data().empty()) { + *o_old_secret = NULL; + } else { + *o_old_secret = tal_arr(tmpctx, struct secret, 1); + unmarshal_secret(rsp.old_secret(), *o_old_secret); + } + STATUS_DEBUG("%s:%d %s { " + "\"self_id\":%s, " + "\"next_per_commitment_point\":%s, " + "\"old_secret\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_pubkey(o_next_per_commitment_point).c_str(), + (*o_old_secret ? + dump_secret(*o_old_secret).c_str() : "")); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_validate_revocation( + struct node_id *peer_id, + u64 dbid, + u64 revoke_num, + struct secret *old_secret) +{ + STATUS_DEBUG( + "%s:%d %s { " + "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " + "\"revoke_num\":%" PRIu64 ", " + "\"old_secret\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_node_id(peer_id).c_str(), + dbid, + revoke_num, + dump_secret(old_secret).c_str() + ); + + last_message = ""; + ValidateCounterpartyRevocationRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); + req.set_revoke_num(revoke_num); + marshal_secret(old_secret, req.mutable_old_secret()); + + ClientContext context; + ValidateCounterpartyRevocationReply rsp; + Status status = stub->ValidateCounterpartyRevocation(&context, req, &rsp); + if (status.ok()) { + STATUS_DEBUG("%s:%d %s { " + "\"self_id\":%s } ", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_cannouncement_sig( + struct node_id *peer_id, + u64 dbid, + u8 *channel_announcement, + secp256k1_ecdsa_signature *o_node_sig, + secp256k1_ecdsa_signature *o_bitcoin_sig) +{ + STATUS_DEBUG( + "%s:%d %s { " + "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " + "\"ca\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_node_id(peer_id).c_str(), + dbid, + dump_hex(channel_announcement, + tal_count(channel_announcement)).c_str() + ); + + /* Skip the portion of the channel_update that we don't sign */ + size_t offset = 2 + 256; /* sizeof(type) + 4*sizeof(signature) */ + size_t annsz = tal_count(channel_announcement); + + last_message = ""; + SignChannelAnnouncementRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); + req.set_channel_announcement(channel_announcement + offset, + annsz - offset); + + ClientContext context; + SignChannelAnnouncementReply rsp; + Status status = stub->SignChannelAnnouncement(&context, req, &rsp); + if (status.ok()) { + unmarshal_ecdsa_signature(rsp.node_signature(), o_node_sig); + unmarshal_ecdsa_signature(rsp.bitcoin_signature(), o_bitcoin_sig); + STATUS_DEBUG("%s:%d %s { " + "\"self_id\":%s, \"node_sig\":%s, " + "\"bitcoin_sig\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_secp256k1_ecdsa_signature(o_node_sig).c_str(), + dump_secp256k1_ecdsa_signature(o_bitcoin_sig).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_sign_local_htlc_tx( + struct bitcoin_tx *tx, + u64 commit_num, + u8 *wscript, + struct node_id *peer_id, + u64 dbid, + struct bitcoin_signature *o_sig) +{ + STATUS_DEBUG( + "%s:%d %s { " + "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " + "\"commit_num\":%" PRIu64 ", \"wscript\":%s, \"tx\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_node_id(peer_id).c_str(), + dbid, + commit_num, + dump_hex(wscript, tal_count(wscript)).c_str(), + dump_tx(tx).c_str() + ); + + last_message = ""; + SignHolderHTLCTxRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); + req.set_n(commit_num); + marshal_single_input_tx(tx, wscript, req.mutable_tx()); + + ClientContext context; + SignatureReply rsp; + Status status = stub->SignHolderHTLCTx(&context, req, &rsp); + if (status.ok()) { + unmarshal_bitcoin_signature(rsp.signature(), o_sig); + STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_bitcoin_signature(o_sig).c_str() + ); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_sign_remote_htlc_tx( + struct bitcoin_tx *tx, + u8 *wscript, + const struct pubkey *remote_per_commit_point, + struct node_id *peer_id, + u64 dbid, + struct bitcoin_signature *o_sig) +{ + STATUS_DEBUG("%s:%d %s { " + "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " + "\"wscript\":%s, \"tx\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_node_id(peer_id).c_str(), + dbid, + dump_hex(wscript, tal_count(wscript)).c_str(), + dump_tx(tx).c_str() + ); + + last_message = ""; + SignCounterpartyHTLCTxRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); + marshal_pubkey(remote_per_commit_point, + req.mutable_remote_per_commit_point()); + marshal_single_input_tx(tx, wscript, req.mutable_tx()); + + ClientContext context; + SignatureReply rsp; + Status status = stub->SignCounterpartyHTLCTx(&context, req, &rsp); + if (status.ok()) { + unmarshal_bitcoin_signature(rsp.signature(), o_sig); + STATUS_DEBUG("%s:%d %s { \"self_id\":%s. \"sig\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_bitcoin_signature(o_sig).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_sign_delayed_payment_to_us( + struct bitcoin_tx *tx, + u64 commit_num, + u8 *wscript, + struct node_id *peer_id, + u64 dbid, + struct bitcoin_signature *o_sig) +{ + STATUS_DEBUG("%s:%d %s { " + "\"self_id\":%s, \"peer_id\":%s, dbid=%" PRIu64 ", " + "\"commit_num\":=%" PRIu64 ", " + "\"wscript\":%s, \"tx\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_node_id(peer_id).c_str(), + dbid, + commit_num, + dump_hex(wscript, tal_count(wscript)).c_str(), + dump_tx(tx).c_str() + ); + + last_message = ""; + SignDelayedSweepRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); + req.set_input(0); + req.set_commitment_number(commit_num); + marshal_single_input_tx(tx, wscript, req.mutable_tx()); + + ClientContext context; + SignatureReply rsp; + Status status = stub->SignDelayedSweep(&context, req, &rsp); + if (status.ok()) { + unmarshal_bitcoin_signature(rsp.signature(), o_sig); + STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_bitcoin_signature(o_sig).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_sign_remote_htlc_to_us( + struct bitcoin_tx *tx, + u8 *wscript, + const struct pubkey *remote_per_commit_point, + struct node_id *peer_id, + u64 dbid, + struct bitcoin_signature *o_sig) +{ + STATUS_DEBUG("%s:%d %s { " + "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " + "\"wscript\":%s, \"tx\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_node_id(peer_id).c_str(), + dbid, + dump_hex(wscript, tal_count(wscript)).c_str(), + dump_tx(tx).c_str() + ); + + last_message = ""; + SignCounterpartyHTLCSweepRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); + req.set_input(0); + marshal_pubkey(remote_per_commit_point, + req.mutable_remote_per_commit_point()); + marshal_single_input_tx(tx, wscript, req.mutable_tx()); + + ClientContext context; + SignatureReply rsp; + Status status = stub->SignCounterpartyHTLCSweep(&context, req, &rsp); + if (status.ok()) { + unmarshal_bitcoin_signature(rsp.signature(), o_sig); + STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_bitcoin_signature(o_sig).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_sign_penalty_to_us( + struct bitcoin_tx *tx, + struct secret *revocation_secret, + u8 *wscript, + struct node_id *peer_id, + u64 dbid, + struct bitcoin_signature *o_sig) +{ + STATUS_DEBUG( + "%s:%d %s { " + "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " + "\"revocation_secret\":%s, \"wscript\":%s, \"tx\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_node_id(peer_id).c_str(), + dbid, + dump_hex(revocation_secret->data, + sizeof(revocation_secret->data)).c_str(), + dump_hex(wscript, tal_count(wscript)).c_str(), + dump_tx(tx).c_str() + ); + + last_message = ""; + SignJusticeSweepRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); + marshal_secret(revocation_secret, req.mutable_revocation_secret()); + req.set_input(0); + marshal_single_input_tx(tx, wscript, req.mutable_tx()); + + ClientContext context; + SignatureReply rsp; + Status status = stub->SignJusticeSweep(&context, req, &rsp); + if (status.ok()) { + unmarshal_bitcoin_signature(rsp.signature(), o_sig); + STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_bitcoin_signature(o_sig).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_check_future_secret( + struct node_id *peer_id, + u64 dbid, + u64 n, + struct secret *suggested, + bool *o_correct) +{ + STATUS_DEBUG( + "%s:%d %s { \"self_id\":%s, \"peer_id\":%s, " + "\"dbid\":%" PRIu64 ", " + "\"n\":%" PRIu64 ", \"suggested\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_node_id(peer_id).c_str(), + dbid, + n, + dump_hex(suggested->data, sizeof(suggested->data)).c_str() + ); + + last_message = ""; + CheckFutureSecretRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); + req.set_n(n); + marshal_secret(suggested, req.mutable_suggested()); + + ClientContext context; + CheckFutureSecretReply rsp; + Status status = stub->CheckFutureSecret(&context, req, &rsp); + if (status.ok()) { + *o_correct = rsp.correct(); + STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"correct\":%d }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), int(*o_correct)); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +proxy_stat proxy_handle_sign_node_announcement( + u8 *node_announcement, + secp256k1_ecdsa_signature *o_sig) +{ + STATUS_DEBUG( + "%s:%d %s { \"self_id\":%s, \"ann\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_hex(node_announcement, + tal_count(node_announcement)).c_str() + ); + + /* Skip the portion of the channel_update that we don't sign */ + size_t offset = 2 + 64; /* sizeof(type) + sizeof(signature) */ + size_t annsz = tal_count(node_announcement); + + last_message = ""; + SignNodeAnnouncementRequest req; + marshal_node_id(&self_id, req.mutable_node_id()); + req.set_node_announcement(node_announcement + offset, annsz - offset); + + ClientContext context; + NodeSignatureReply rsp; + Status status = stub->SignNodeAnnouncement(&context, req, &rsp); + if (status.ok()) { + unmarshal_ecdsa_signature(rsp.signature(), o_sig); + STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + dump_secp256k1_ecdsa_signature(o_sig).c_str()); + last_message = "success"; + return PROXY_OK; + } else { + status_unusual("%s:%d %s: self_id=%s %s", + __FILE__, __LINE__, __FUNCTION__, + dump_node_id(&self_id).c_str(), + status.error_message().c_str()); + last_message = status.error_message(); + return map_status(status); + } +} + +// FIXME - These routines allows us to pretty print to stderr from C +// code. Probably should remove it in production ... + +void print_tx(char const *tag, struct bitcoin_tx const *tx) +{ + fprintf(stderr, "%s: bitcoin_tx=%s\n", tag, dump_tx(tx).c_str()); +} + +void print_psbt(char const *tag, const struct wally_psbt *psbt) +{ + fprintf(stderr, "%s: wally_psbt=%s\n", + tag, dump_wally_psbt(psbt).c_str()); +} + +} /* extern "C" */ diff --git a/contrib/remote_hsmd/proxy.hpp b/contrib/remote_hsmd/proxy.hpp new file mode 100644 index 000000000000..cbfd3cfcbba4 --- /dev/null +++ b/contrib/remote_hsmd/proxy.hpp @@ -0,0 +1,231 @@ +#ifndef LIGHTNING_CONTRIB_REMOTE_HSMD_PROXY_H +#define LIGHTNING_CONTRIB_REMOTE_HSMD_PROXY_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +struct bip32_key_version; +struct channel_type; +struct utxo; +struct simple_htlc; + +#define STATUS_DEBUG(args...) \ + do { \ + fprintf(stderr, args); \ + fprintf(stderr, "\n"); \ + } while (false) + +struct bip32_key_version; +struct utxo; +struct witscript; + +enum proxy_status { + /* SUCCESS */ + PROXY_OK = 0, + + /* TRANSIENT */ + PROXY_TIMEOUT = 32, + PROXY_UNAVAILABLE = 33, + PROXY_CANCELLED = 34, + + /* PERMANENT */ + PROXY_INVALID_ARGUMENT = 100, + PROXY_INTERNAL_ERROR = 200, +}; +typedef enum proxy_status proxy_stat; + +#define PROXY_SUCCESS(rv) ((rv) < 32) +#define PROXY_TRANSIENT(rv) ((rv) >= 32 && (rv) < 100) +#define PROXY_PERMANENT(rv) ((rv) >= 100) + +char const *proxy_last_message(void); + +void proxy_setup(void); + +void proxy_set_node_id(const struct node_id *node_id); + +proxy_stat proxy_init_hsm( + struct bip32_key_version *bip32_key_version, + struct chainparams const *chainparams, + bool coldstart, + struct secret *hsm_secret, + struct node_id *o_node_id); + +proxy_stat proxy_get_ext_pub_key(struct ext_key *o_ext_pub_key); + +proxy_stat proxy_handle_ecdh( + const struct pubkey *point, + struct secret *o_ss); + +proxy_stat proxy_handle_pass_client_hsmfd( + struct node_id *peer_id, + u64 dbid, + u64 capabilities); + +proxy_stat proxy_handle_new_channel( + struct node_id *peer_id, + u64 dbid); + +proxy_stat proxy_handle_ready_channel( + struct node_id *peer_id, + u64 dbid, + bool is_outbound, + struct amount_sat *channel_value, + struct amount_msat *push_value, + struct bitcoin_txid *funding_txid, + u16 funding_txout, + u16 local_to_self_delay, + u8 *local_shutdown_script, + u32 *local_shutdown_wallet_index, + struct basepoints *remote_basepoints, + struct pubkey *remote_funding_pubkey, + u16 remote_to_self_delay, + u8 *remote_shutdown_script, + struct channel_type *channel_type); + +proxy_stat proxy_handle_sign_withdrawal_tx( + struct bitcoin_tx_output **outputs, + struct utxo **utxos, + struct wally_psbt *psbt, + u8 ****o_wits); + +proxy_stat proxy_handle_sign_remote_commitment_tx( + struct bitcoin_tx *tx, + const struct pubkey *remote_funding_pubkey, + struct node_id *peer_id, + u64 dbid, + const struct pubkey *remote_per_commit, + struct simple_htlc **htlc, + u64 commit_num, + u32 feerate, + struct bitcoin_signature *o_sig); + +proxy_stat proxy_handle_get_per_commitment_point( + struct node_id *peer_id, + u64 dbid, + u64 n, + struct pubkey *o_per_commitment_point, + struct secret **o_old_secret); + +proxy_stat proxy_handle_sign_invoice( + u5 *u5bytes, + u8 *hrpu8, + secp256k1_ecdsa_recoverable_signature *o_sig); + +proxy_stat proxy_handle_sign_message( + u8 *msg, + secp256k1_ecdsa_recoverable_signature *o_sig); + +proxy_stat proxy_handle_get_channel_basepoints( + struct node_id *peer_id, + u64 dbid, + struct basepoints *o_basepoints, + struct pubkey *o_funding_pubkey); + +proxy_stat proxy_handle_sign_mutual_close_tx( + struct bitcoin_tx *tx, + const struct pubkey *remote_funding_pubkey, + struct node_id *peer_id, + u64 dbid, + struct bitcoin_signature *o_sig); + +proxy_stat proxy_handle_sign_commitment_tx( + struct node_id *peer_id, + u64 dbid, + u64 commit_num, + struct bitcoin_signature *o_sig); + +proxy_stat proxy_handle_validate_commitment_tx( + struct bitcoin_tx *tx, + struct node_id *peer_id, + u64 dbid, + struct simple_htlc **htlc, + u64 commit_num, + u32 feerate, + struct bitcoin_signature *commit_sig, + struct bitcoin_signature *htlc_sigs, + struct secret **o_old_secret, + struct pubkey *next_per_commitment_point); + +proxy_stat proxy_handle_validate_revocation( + struct node_id *peer_id, + u64 dbid, + u64 revoke_num, + struct secret *old_secret); + +proxy_stat proxy_handle_cannouncement_sig( + struct node_id *peer_id, + u64 dbid, + u8 *channel_announcement, + secp256k1_ecdsa_signature *o_node_sig, + secp256k1_ecdsa_signature *o_bitcoin_sig); + +proxy_stat proxy_handle_channel_update_sig( + u8 *channel_update, + secp256k1_ecdsa_signature *o_sig); + +proxy_stat proxy_handle_sign_local_htlc_tx( + struct bitcoin_tx *tx, + u64 commit_num, + u8 *wscript, + struct node_id *peer_id, + u64 dbid, + struct bitcoin_signature *o_sig); + +proxy_stat proxy_handle_sign_remote_htlc_tx( + struct bitcoin_tx *tx, + u8 *wscript, + const struct pubkey *remote_per_commit_point, + struct node_id *peer_id, + u64 dbid, + struct bitcoin_signature *o_sig); + +proxy_stat proxy_handle_sign_delayed_payment_to_us( + struct bitcoin_tx *tx, + u64 commit_num, + u8 *wscript, + struct node_id *peer_id, + u64 dbid, + struct bitcoin_signature *o_sig); + +proxy_stat proxy_handle_sign_remote_htlc_to_us( + struct bitcoin_tx *tx, + u8 *wscript, + const struct pubkey *remote_per_commit_point, + struct node_id *peer_id, + u64 dbid, + struct bitcoin_signature *o_sig); + +proxy_stat proxy_handle_sign_penalty_to_us( + struct bitcoin_tx *tx, + struct secret *revocation_secret, + u8 *wscript, + struct node_id *peer_id, + u64 dbid, + struct bitcoin_signature *o_sig); + +proxy_stat proxy_handle_check_future_secret( + struct node_id *peer_id, + u64 dbid, + u64 n, + struct secret *suggested, + bool *o_correct); + +proxy_stat proxy_handle_sign_node_announcement( + u8 *node_announcement, + secp256k1_ecdsa_signature *o_sig); + +// FIXME - For debugging, remove for production. +void print_tx(char const *tag, struct bitcoin_tx const *tx); +void print_psbt(char const *tag, const struct wally_psbt *psbt); + +#ifdef __cplusplus +} /* extern C */ +#endif + +#endif /* LIGHTNING_CONTRIB_REMOTE_HSMD_PROXY_H */ diff --git a/external/.gitignore b/external/.gitignore index fd9b925c84cc..f74c98d52486 100644 --- a/external/.gitignore +++ b/external/.gitignore @@ -1,4 +1,4 @@ -x86_64-linux-gnu +/x86_64-redhat-linux aarch64-linux-gnu arm-linux-gnueabihf x86_64-pc-linux-gnu @@ -16,3 +16,4 @@ libsecp256k1.a libsecp256k1.la libwallycore.a libwallycore.la +x86_64-linux-gnu diff --git a/lightningd/.gitignore b/lightningd/.gitignore index 4ba7a1a9cec2..67b3ee6cfd3c 100644 --- a/lightningd/.gitignore +++ b/lightningd/.gitignore @@ -8,3 +8,4 @@ lightning_hsmd lightning_onchaind lightning_openingd lightning_websocketd +remote_hsmd From e508cfb5fee5ece6f9d6c638ec73a522a58ff9c3 Mon Sep 17 00:00:00 2001 From: Richard Ulrich Date: Mon, 24 May 2021 01:16:46 +0200 Subject: [PATCH 02/53] read the GRPC endpoint from the env var REMOTE_HSMD_ENDPOINT (#22) --- contrib/remote_hsmd/proxy.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/contrib/remote_hsmd/proxy.cc b/contrib/remote_hsmd/proxy.cc index 4d0b928e3bba..f9638755f58b 100644 --- a/contrib/remote_hsmd/proxy.cc +++ b/contrib/remote_hsmd/proxy.cc @@ -350,8 +350,11 @@ const char *proxy_last_message(void) void proxy_setup() { - STATUS_DEBUG("%s:%d %s pid:%d", __FILE__, __LINE__, __FUNCTION__, getpid()); - auto channel = grpc::CreateChannel("localhost:50051", + const char *endpointvar = getenv("REMOTE_HSMD_ENDPOINT"); + const char *endpoint = endpointvar != NULL ? endpointvar : "localhost:50051"; + STATUS_DEBUG("%s:%d %s pid:%d endpoint:%s", + __FILE__, __LINE__, __FUNCTION__, getpid(), endpoint); + auto channel = grpc::CreateChannel(endpoint, grpc::InsecureChannelCredentials()); stub = Signer::NewStub(channel); last_message = ""; From 06b4208f552287e6d59b0d529758799d1b26639c Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Tue, 15 Mar 2022 21:07:24 -0700 Subject: [PATCH 03/53] Integrate VLSD into integration test framework --- Makefile | 4 + contrib/pyln-testing/pyln/testing/fixtures.py | 2 +- contrib/pyln-testing/pyln/testing/utils.py | 89 +++++++++++++++++-- contrib/remote_hsmd/TESTING_ALLOWLIST | 41 +++++++++ contrib/remote_hsmd/scripts/cleanup | 10 +++ .../remote_hsmd/scripts/rerun-failed-tests | 23 +++++ contrib/remote_hsmd/scripts/run-all-tests | 17 ++++ contrib/remote_hsmd/scripts/run-one-test | 19 ++++ 8 files changed, 199 insertions(+), 6 deletions(-) create mode 100644 contrib/remote_hsmd/TESTING_ALLOWLIST create mode 100755 contrib/remote_hsmd/scripts/cleanup create mode 100755 contrib/remote_hsmd/scripts/rerun-failed-tests create mode 100755 contrib/remote_hsmd/scripts/run-all-tests create mode 100755 contrib/remote_hsmd/scripts/run-one-test diff --git a/Makefile b/Makefile index 285f43a02f52..520cc0e8a30f 100644 --- a/Makefile +++ b/Makefile @@ -446,6 +446,10 @@ else PYTEST_OPTS += -x endif +ifneq ($(PYTEST_MOREOPTS),) +PYTEST_OPTS += $(PYTEST_MOREOPTS) +endif + check-units: check: check-units installcheck check-protos pytest diff --git a/contrib/pyln-testing/pyln/testing/fixtures.py b/contrib/pyln-testing/pyln/testing/fixtures.py index bf3293d24452..e57852a5fa5a 100644 --- a/contrib/pyln-testing/pyln/testing/fixtures.py +++ b/contrib/pyln-testing/pyln/testing/fixtures.py @@ -92,7 +92,7 @@ def directory(request, test_base_dir, test_name): outcome = 'passed' if rep_call is None else rep_call.outcome failed = not outcome or request.node.has_errors or outcome != 'passed' - if not failed: + if not failed and not bool(int(os.getenv('TEST_KEEPDIR', '0'))): try: shutil.rmtree(directory) except OSError: diff --git a/contrib/pyln-testing/pyln/testing/utils.py b/contrib/pyln-testing/pyln/testing/utils.py index 00194e2d3872..0017a6acae65 100644 --- a/contrib/pyln-testing/pyln/testing/utils.py +++ b/contrib/pyln-testing/pyln/testing/utils.py @@ -80,6 +80,7 @@ def env(name, default=None): SLOW_MACHINE = env("SLOW_MACHINE", "0") == "1" DEPRECATED_APIS = env("DEPRECATED_APIS", "0") == "1" TIMEOUT = int(env("TIMEOUT", 180 if SLOW_MACHINE else 60)) +SUBDAEMON = env("SUBDAEMON", "") EXPERIMENTAL_DUAL_FUND = env("EXPERIMENTAL_DUAL_FUND", "0") == "1" @@ -579,6 +580,40 @@ def getnewaddress(self): return info['unconfidential'] +class ValidatingLightningSignerD(TailableProc): + def __init__(self, vlsd_dir, vlsd_port): + TailableProc.__init__(self, vlsd_dir) + self.executable = env("REMOTE_SIGNER_CMD", 'vlsd') + self.opts = [ + '--log-level-console=DEBUG', + '--log-level-disk=DEBUG', + '--network={}'.format(TEST_NETWORK), + '--datadir={}'.format(vlsd_dir), + '--port={}'.format(vlsd_port), + '--initial-allowlist-file={}'.format(env('REMOTE_SIGNER_ALLOWLIST', + 'contrib/remote_hsmd/TESTING_ALLOWLIST')), + ] + self.prefix = 'vlsd' + self.vlsd_port = vlsd_port + + @property + def cmd_line(self): + return [self.executable] + self.opts + + def start(self, stdin=None, stdout=None, stderr=None, + wait_for_initialized=True): + TailableProc.start(self, stdin, stdout, stderr) + # We need to always wait for initialization + self.wait_for_log("vlsd [0-9]* ready on .*:{}".format(self.vlsd_port)) + logging.info("vlsd started") + + def stop(self, timeout=10): + logging.info("stopping vlsd") + rc = TailableProc.stop(self, timeout) + logging.info("vlsd stopped") + return rc + + class LightningD(TailableProc): def __init__( self, @@ -596,6 +631,9 @@ def __init__( self.port = port self.cmd_prefix = [] self.disconnect_file = None + self.lightning_dir = lightning_dir + self.use_vlsd = False; + self.vlsd_dir = os.path.join(lightning_dir, "vlsd") self.rpcproxy = bitcoindproxy self.env['CLN_PLUGIN_LOG'] = "cln_plugin=trace,cln_rpc=trace,cln_grpc=trace,debug" @@ -618,12 +656,21 @@ def __init__( if grpc_port is not None: opts['grpc-port'] = grpc_port + if SUBDAEMON: + opts['subdaemon'] = SUBDAEMON + if SUBDAEMON == 'hsmd:remote_hsmd': + self.use_vlsd = True + for k, v in opts.items(): self.opts[k] = v if not os.path.exists(os.path.join(lightning_dir, TEST_NETWORK)): os.makedirs(os.path.join(lightning_dir, TEST_NETWORK)) + if self.use_vlsd: + if not os.path.exists(self.vlsd_dir): + os.makedirs(self.vlsd_dir) + # Last 32-bytes of final part of dir -> seed. seed = (bytes(re.search('([^/]+)/*$', lightning_dir).group(1), encoding='utf-8') + bytes(32))[:32] if not random_hsm: @@ -640,6 +687,11 @@ def __init__( self.early_opts = [] def cleanup(self): + if self.use_vlsd: + if self.use_vlsd: + # Make sure the remotesigner is shutdown + self.vlsd.stop() + # To force blackhole to exit, disconnect file must be truncated! if self.disconnect_file: with open(self.disconnect_file, "w") as f: @@ -661,11 +713,38 @@ def cmd_line(self): return self.cmd_prefix + [self.executable] + self.early_opts + opts def start(self, stdin=None, wait_for_initialized=True, stderr_redir=False): - self.opts['bitcoin-rpcport'] = self.rpcproxy.rpcport - TailableProc.start(self, stdin, stdout_redir=False, stderr_redir=stderr_redir) - if wait_for_initialized: - self.wait_for_log("Server started with public key") - logging.info("LightningD started") + try: + if self.use_vlsd: + # Start the remote signer first + vlsd_port = reserve() + self.vlsd = ValidatingLightningSignerD(self.vlsd_dir, vlsd_port) + self.vlsd.start(stdin, stdout, stderr, wait_for_initialized) + + # We can't do this in the constructor because we need a new port on each restart. + self.env['REMOTE_HSMD_ENDPOINT'] = '127.0.0.1:{}'.format(vlsd_port) + + self.opts['bitcoin-rpcport'] = self.rpcproxy.rpcport + TailableProc.start(self, stdin, stdout_redir=False, stderr_redir=stderr_redir) + if wait_for_initialized: + self.wait_for_log("Server started with public key") + logging.info("LightningD started") + except Exception: + if self.use_vlsd: + # LightningD didn't start, stop the remotesigner + self.vlsd.stop() + raise + + def stop(self, timeout=10): + if self.use_vlsd: + # Stop the remote signer first + self.vlsd.stop(timeout) + return TailableProc.stop(self, timeout) + + def kill(self): + if self.use_vlsd: + # Kill the remote signer first + self.vlsd.kill() + TailableProc.kill(self) def wait(self, timeout=TIMEOUT): """Wait for the daemon to stop for up to timeout seconds diff --git a/contrib/remote_hsmd/TESTING_ALLOWLIST b/contrib/remote_hsmd/TESTING_ALLOWLIST new file mode 100644 index 000000000000..ca919f4ba7f8 --- /dev/null +++ b/contrib/remote_hsmd/TESTING_ALLOWLIST @@ -0,0 +1,41 @@ +bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg +bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080 +bcrt1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qzf4jry +bcrt1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k0ylj56 +bcrt1sw50qt2uwha +bcrt1zw508d6qejxtdg4y5r3zarvaryv2wuatf +bcrt1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvseswlauz7 +bcrt1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesyga46z +bcrt1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqc8gma6 +bcrt1pqqqqrldkrl +bcrt1pqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqwekeum +bcrt1sqqqq4wstyw +bcrt1sqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq8v496j +bcrt1zqqqq0u8uvu +bcrt1zqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqla7vv2 +bcrt1rqqqqtap6fa +bcrt1rqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsxpcm9 +bcrt1yqqqqh6ngj6 +bcrt1yqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq54w09p +bcrt19qqqqnm4whm +bcrt19qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmw3mjw +bcrt1xqqqqlclycc +bcrt1xqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq22ewzl +bcrt18qqqqmeezae +bcrt18qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq93x64s +bcrt1gqqqqwkjf8k +bcrt1gqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqz98fhh +bcrt1fqqqq2h50zh +bcrt1fqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqd7caqc +bcrt12qqqqx579d5 +bcrt12qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqu6sgsf +bcrt1tqqqqz4crg4 +bcrt1tqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnp0u8x +bcrt1vqqqq7j23nj +bcrt1vqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhjqtez +bcrt1dqqqq6nvhkn +bcrt1dqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqcfllwd +bcrt1wqqqqksxaes +bcrt1wqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfdh27u +bcrt10qqqqj3qmu3 +bcrt10qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqxkg7fn diff --git a/contrib/remote_hsmd/scripts/cleanup b/contrib/remote_hsmd/scripts/cleanup new file mode 100755 index 000000000000..b618ace616ff --- /dev/null +++ b/contrib/remote_hsmd/scripts/cleanup @@ -0,0 +1,10 @@ +#!/usr/bin/sh + +rm -rf /tmp/ltests-* +rm -rf /tmp/lnpt-cl-* +rm -rf /tmp/tmp* +rm -rf /tmp/.tmp* +rm -rf /tmp/vgdb-pipe-* +rm -rf /tmp/git-difftool.* +killall bitcoind +killall vlsd diff --git a/contrib/remote_hsmd/scripts/rerun-failed-tests b/contrib/remote_hsmd/scripts/rerun-failed-tests new file mode 100755 index 000000000000..0e936a4ee776 --- /dev/null +++ b/contrib/remote_hsmd/scripts/rerun-failed-tests @@ -0,0 +1,23 @@ +#!/usr/bin/sh + +echo "accepts log w/ failures on stdin" + +TESTS=`awk '/^FAILED tests/ {print $2}'` + +# scale the parallelism to number of processors +NPROC=$(nproc) +TPAR=$((NPROC * 2)) + +PYTHONPATH=\ +$PWD/contrib/pyln-client:\ +$PWD/contrib/pyln-testing:\ +$PWD/contrib/pyln-proto \ +TEST_DEBUG=1 \ +DEVELOPER=1 \ +VALGRIND=0 \ +SUBDAEMON='hsmd:remote_hsmd' \ +REMOTE_SIGNER_CMD=$(pwd)/../validating-lightning-signer/target/debug/vlsd \ +REMOTE_SIGNER_ALLOWLIST=$(pwd)/contrib/remote_hsmd/TESTING_ALLOWLIST \ +pytest \ +$TESTS \ +-n=$TPAR --timeout=300 --timeout_method=thread -v diff --git a/contrib/remote_hsmd/scripts/run-all-tests b/contrib/remote_hsmd/scripts/run-all-tests new file mode 100755 index 000000000000..144326b2aa91 --- /dev/null +++ b/contrib/remote_hsmd/scripts/run-all-tests @@ -0,0 +1,17 @@ +#!/usr/bin/sh + +# scale the parallelism to number of processors +JPAR=$(nproc) +TPAR=$((JPAR * 2)) + +SUBDAEMON='hsmd:remote_hsmd' \ +REMOTE_SIGNER_CMD=$(pwd)/../validating-lightning-signer/target/debug/vlsd \ +REMOTE_SIGNER_ALLOWLIST=$(pwd)/contrib/remote_hsmd/TESTING_ALLOWLIST \ +make \ +-j$JPAR PYTEST_PAR=$TPAR \ +DEVELOPER=1 \ +VALGRIND=0 \ +SLOW_MACHINE=0 \ +PYTEST_MOREOPTS='--timeout=300 --timeout_method=thread' \ +pytest + diff --git a/contrib/remote_hsmd/scripts/run-one-test b/contrib/remote_hsmd/scripts/run-one-test new file mode 100755 index 000000000000..69feb9192411 --- /dev/null +++ b/contrib/remote_hsmd/scripts/run-one-test @@ -0,0 +1,19 @@ +#!/usr/bin/sh + +THETEST=$1 + +PYTHONPATH=\ +$PWD/contrib/pyln-client:\ +$PWD/contrib/pyln-testing:\ +$PWD/contrib/pyln-proto \ +TEST_KEEPDIR=1 \ +TEST_DEBUG=1 \ +DEVELOPER=1 \ +VALGRIND=0 \ +SLOW_MACHINE=0 \ +SUBDAEMON='hsmd:remote_hsmd' \ +REMOTE_SIGNER_CMD=$(pwd)/../validating-lightning-signer/target/debug/vlsd \ +REMOTE_SIGNER_ALLOWLIST=$(pwd)/contrib/remote_hsmd/TESTING_ALLOWLIST \ +pytest \ +$THETEST \ +-v --timeout=300 --timeout_method=thread -x -s From b7eaabc489aa48ebcd14bbebb644cd0be0c1b87b Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Wed, 16 Mar 2022 00:18:53 -0700 Subject: [PATCH 04/53] Skip test which are not valid w/ VLSD --- tests/test_closing.py | 10 ++++++++++ tests/test_connection.py | 3 +++ tests/test_db.py | 5 +++++ tests/test_misc.py | 6 ++++++ tests/test_opening.py | 4 ++++ tests/test_pay.py | 7 +++++++ tests/test_plugin.py | 1 + tests/test_wallet.py | 8 ++++++++ 8 files changed, 44 insertions(+) diff --git a/tests/test_closing.py b/tests/test_closing.py index 022daf2fc656..5b0cc883812a 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -740,6 +740,7 @@ def test_penalty_outhtlc(node_factory, bitcoind, executor, chainparams): @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "handle_sign_option_will_fund_offer unimplemented") @pytest.mark.openchannel('v2') @pytest.mark.slow_test @pytest.mark.developer("requres 'dev-queryrates', 'dev-force-features'") @@ -784,6 +785,7 @@ def test_channel_lease_falls_behind(node_factory, bitcoind): @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "handle_sign_option_will_fund_offer unimplemented") @pytest.mark.openchannel('v2') @pytest.mark.developer("requres 'dev-queryrates', 'dev-force-features'") @pytest.mark.slow_test @@ -898,6 +900,7 @@ def test_channel_lease_post_expiry(node_factory, bitcoind, chainparams): @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "handle_sign_option_will_fund_offer unimplemented") @pytest.mark.openchannel('v2') @pytest.mark.slow_test @pytest.mark.developer("requres 'dev-queryrates', 'dev-force-features'") @@ -1009,6 +1012,7 @@ def test_channel_lease_unilat_closes(node_factory, bitcoind): @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "handle_sign_option_will_fund_offer unimplemented") @pytest.mark.openchannel('v2') @unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "Makes use of the sqlite3 db") @pytest.mark.developer("requres 'dev-queryrates', 'dev-force-features'") @@ -1088,6 +1092,7 @@ def test_channel_lease_lessor_cheat(node_factory, bitcoind, chainparams): @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "handle_sign_option_will_fund_offer unimplemented") @pytest.mark.openchannel('v2') @unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "Makes use of the sqlite3 db") @pytest.mark.developer("requres 'dev-queryrates', dev-no-reconnect, dev-force-features") @@ -1167,6 +1172,7 @@ def test_channel_lease_lessee_cheat(node_factory, bitcoind, chainparams): @pytest.mark.developer("needs DEVELOPER=1") @unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "Makes use of the sqlite3 db") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't sign revoked commitment number") @pytest.mark.slow_test def test_penalty_htlc_tx_fulfill(node_factory, bitcoind, chainparams): """ Test that the penalizing node claims any published @@ -1346,6 +1352,7 @@ def test_penalty_htlc_tx_fulfill(node_factory, bitcoind, chainparams): @pytest.mark.developer("needs DEVELOPER=1") @unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "Makes use of the sqlite3 db") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't sign revoked commitment number") @pytest.mark.slow_test def test_penalty_htlc_tx_timeout(node_factory, bitcoind, chainparams): """ Test that the penalizing node claims any published @@ -1568,6 +1575,7 @@ def test_penalty_htlc_tx_timeout(node_factory, bitcoind, chainparams): @pytest.mark.developer("uses dev_sign_last_tx") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "exceeds max fee policy") def test_penalty_rbf_normal(node_factory, bitcoind, executor, chainparams): ''' Test that penalty transactions are RBFed. @@ -3045,6 +3053,7 @@ def test_permfail_htlc_out(node_factory, bitcoind, executor): @pytest.mark.developer("needs DEVELOPER=1") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't withdraw to non-wallet address") def test_permfail(node_factory, bitcoind): l1, l2 = node_factory.line_graph(2) @@ -3146,6 +3155,7 @@ def test_shutdown(node_factory): @pytest.mark.developer("needs to set upfront_shutdown_script") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy failure: validate_mutual_close_tx: holder_script doesn't match upfront holder_shutdown_script") def test_option_upfront_shutdown_script(node_factory, bitcoind, executor): l1 = node_factory.get_node(start=False, allow_warning=True) # Insist on upfront script we're not going to match. diff --git a/tests/test_connection.py b/tests/test_connection.py index b1cf7d970e68..65aa39f39a83 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1657,6 +1657,7 @@ def test_funding_v2_cancel_race(node_factory, bitcoind, executor): @pytest.mark.openchannel('v1') @pytest.mark.openchannel('v2') @unittest.skipIf(TEST_NETWORK != 'regtest', "External wallet support doesn't work with elements yet.") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd can't handle random external addresses (allowlist)") def test_funding_close_upfront(node_factory, bitcoind): opts = {'plugin': os.path.join(os.getcwd(), 'tests/plugins/openchannel_hook_accepter.py')} @@ -2867,6 +2868,7 @@ def mock_donothing(r): @pytest.mark.developer("needs dev_fail") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't withdraw to non-wallet address") def test_no_fee_estimate(node_factory, bitcoind, executor): l1 = node_factory.get_node(start=False, options={'dev-no-fake-fees': True}) @@ -3597,6 +3599,7 @@ def test_channel_features(node_factory, bitcoind): @pytest.mark.developer("need dev-force-features") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't support non-option_static_remotekey") def test_nonstatic_channel(node_factory, bitcoind): """Smoke test for a channel without option_static_remotekey""" l1, l2 = node_factory.line_graph(2, diff --git a/tests/test_db.py b/tests/test_db.py index b6dbfd74d229..27252f40408c 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -136,6 +136,7 @@ def test_max_channel_id(node_factory, bitcoind): @unittest.skipIf(not COMPAT, "needs COMPAT to convert obsolete db") @unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "This test is based on a sqlite3 snapshot") @unittest.skipIf(TEST_NETWORK != 'regtest', "The network must match the DB snapshot") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't like channel_nonce changing") def test_scid_upgrade(node_factory, bitcoind): bitcoind.generate_block(1) @@ -167,6 +168,7 @@ def test_scid_upgrade(node_factory, bitcoind): @unittest.skipIf(not COMPAT, "needs COMPAT to convert obsolete db") @unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "This test is based on a sqlite3 snapshot") @unittest.skipIf(TEST_NETWORK != 'regtest', "The network must match the DB snapshot") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't like channel_nonce changing") def test_last_tx_inflight_psbt_upgrade(node_factory, bitcoind): bitcoind.generate_block(12) @@ -185,6 +187,7 @@ def test_last_tx_inflight_psbt_upgrade(node_factory, bitcoind): @unittest.skipIf(not COMPAT, "needs COMPAT to convert obsolete db") @unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "This test is based on a sqlite3 snapshot") @unittest.skipIf(TEST_NETWORK != 'regtest', "The network must match the DB snapshot") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't like channel_nonce changing") def test_last_tx_psbt_upgrade(node_factory, bitcoind): bitcoind.generate_block(12) @@ -219,6 +222,7 @@ def test_last_tx_psbt_upgrade(node_factory, bitcoind): @pytest.mark.slow_test @unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "This test is based on a sqlite3 snapshot") @unittest.skipIf(TEST_NETWORK != 'regtest', "The network must match the DB snapshot") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't support backfill yet") def test_backfill_scriptpubkeys(node_factory, bitcoind): bitcoind.generate_block(214) @@ -353,6 +357,7 @@ def get_dsn(self): os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "This test is based on a sqlite3 snapshot" ) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't support DB migration") def test_local_basepoints_cache(bitcoind, node_factory): """XXX started caching the local basepoints as well as the remote ones. diff --git a/tests/test_misc.py b/tests/test_misc.py index c4b0ff076478..0789f8b84980 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -470,6 +470,7 @@ def is_p2wpkh(output): assert only_one(fundingtx['vin'])['txid'] == res['wallettxid'] +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't withdraw to non-wallet address") def test_withdraw_misc(node_factory, bitcoind, chainparams): def dont_spend_outputs(n, txid): """Reserve both outputs (we assume there are two!) in case any our ours, so we don't spend change: wrecks accounting checks""" @@ -1167,6 +1168,7 @@ def test_cli_commando(node_factory): assert only_one(j['invoices'])['label'] == 'l"[]{}' +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd integration test job control fails here") def test_daemon_option(node_factory): """ Make sure --daemon at least vaguely works! @@ -1203,6 +1205,7 @@ def test_daemon_option(node_factory): @pytest.mark.developer("needs DEVELOPER=1") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't have repeatable random seeding") def test_blockchaintrack(node_factory, bitcoind): """Check that we track the blockchain correctly across reorgs """ @@ -1713,6 +1716,7 @@ def check_new_log(): @unittest.skipIf(VALGRIND, "Valgrind sometimes fails assert on injected SEGV") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "temporary, until fedora-34 gets crashlog libs fixed") def test_crashlog(node_factory): l1 = node_factory.get_node(may_fail=True, allow_broken_log=True) @@ -2069,6 +2073,7 @@ def test_bitcoind_feerate_floor(node_factory, bitcoind): @pytest.mark.developer("needs --dev-force-bip32-seed") @unittest.skipIf(TEST_NETWORK != 'regtest', "Addresses are network specific") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't support forced secrets") def test_dev_force_bip32_seed(node_factory): l1 = node_factory.get_node(options={'dev-force-bip32-seed': '0000000000000000000000000000000000000000000000000000000000000001'}) # First is m/0/0/1 .. @@ -2417,6 +2422,7 @@ def test_regtest_upgrade(node_factory): @unittest.skipIf(VALGRIND, "valgrind files can't be written since we rmdir") @unittest.skipIf(TEST_NETWORK != "regtest", "needs bitcoin mainnet") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't create hsm_secret file") def test_new_node_is_mainnet(node_factory): """Test that an empty directory causes us to be on mainnet""" l1 = node_factory.get_node(start=False, may_fail=True) diff --git a/tests/test_opening.py b/tests/test_opening.py index bc9b97f92fb8..27506bb8d74d 100644 --- a/tests/test_opening.py +++ b/tests/test_opening.py @@ -7,6 +7,7 @@ from pathlib import Path from pprint import pprint +import os import pytest import re import unittest @@ -18,6 +19,7 @@ def find_next_feerate(node, peer): @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "handle_sign_option_will_fund_offer unimplemented") @pytest.mark.openchannel('v2') @pytest.mark.developer("requres 'dev-queryrates' + 'dev-force-features'") def test_queryrates(node_factory, bitcoind): @@ -377,6 +379,7 @@ def test_v2_rbf_single(node_factory, bitcoind, chainparams): @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "handle_sign_option_will_fund_offer unimplemented") @pytest.mark.openchannel('v2') @pytest.mark.developer("requres 'dev-force-features'") def test_v2_rbf_liquidity_ad(node_factory, bitcoind, chainparams): @@ -1200,6 +1203,7 @@ def test_funder_options(node_factory, bitcoind): @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "dual-funding not supported yet") def test_funder_contribution_limits(node_factory, bitcoind): opts = {'experimental-dual-fund': None, 'feerates': (5000, 5000, 5000, 5000)} diff --git a/tests/test_pay.py b/tests/test_pay.py index 2c7b2a543d38..2b97f8506dd8 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -2706,6 +2706,7 @@ def test_htlc_too_dusty_outgoing(node_factory, bitcoind, chainparams): l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret']) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "fee above maximum: 81720 > 80000") @pytest.mark.developer("needs DEVELOPER=1 for dev_ignore_htlcs") def test_htlc_too_dusty_incoming(node_factory, bitcoind): """ Try to hit the 'too much dust' limit, should fail the HTLC """ @@ -4313,6 +4314,7 @@ def test_large_mpp_presplit(node_factory): @pytest.mark.developer("builds large network, which is slow if not DEVELOPER") @pytest.mark.slow_test +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't allow push of greater than 20k sat") def test_mpp_overload_payee(node_factory, bitcoind): """ We had a bug where if the payer is unusually well-connected compared @@ -4383,6 +4385,7 @@ def test_offer_needs_option(node_factory): assert l1.rpc.decode('lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcgqyqs5pr5v4ehg93pqfnwgkvdr57yzh6h92zg3qctvrm7w38djg67kzcm4yeg8vc4cq63s')['valid'] +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't support bolt12 yet") def test_offer(node_factory, bitcoind): plugin = os.path.join(os.path.dirname(__file__), 'plugins/currencyUSDAUD5000.py') l1 = node_factory.get_node(options={'plugin': plugin, 'experimental-offers': None}) @@ -4561,6 +4564,7 @@ def test_offer_deprecated_api(node_factory, bitcoind): l1.rpc.pay(inv['invoice']) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't support bolt12 yet") @pytest.mark.developer("dev-no-modern-onion is DEVELOPER-only") def test_fetchinvoice_3hop(node_factory, bitcoind): l1, l2, l3, l4 = node_factory.line_graph(4, wait_for_announce=True, @@ -4574,6 +4578,7 @@ def test_fetchinvoice_3hop(node_factory, bitcoind): l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12']}) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't support bolt12 yet") def test_fetchinvoice(node_factory, bitcoind): # We remove the conversion plugin on l3, causing it to get upset. l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True, @@ -4794,6 +4799,7 @@ def test_fetchinvoice_recurrence(node_factory, bitcoind): @pytest.mark.developer("Needs dev-allow-localhost for autoconnect, dev-force-features to avoid routing onionmsgs") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't support bolt12 yet") def test_fetchinvoice_autoconnect(node_factory, bitcoind): """We should autoconnect if we need to, to route.""" @@ -4860,6 +4866,7 @@ def test_pay_waitblockheight_timeout(node_factory, bitcoind): @pytest.mark.developer("dev-rawrequest is DEVELOPER-only") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't support bolt12 yet") def test_dev_rawrequest(node_factory): l1, l2 = node_factory.line_graph(2, fundchannel=False, opts={'experimental-offers': None}) diff --git a/tests/test_plugin.py b/tests/test_plugin.py index d53382b6d1c5..05e0de1ed4db 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -2124,6 +2124,7 @@ def test_3847_repro(node_factory, bitcoind): l1.rpc.paystatus(i1) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "trouble managing remotesigner when node killed this way") def test_important_plugin(node_factory): # Cache it here. pluginsdir = os.path.join(os.path.dirname(__file__), "plugins") diff --git a/tests/test_wallet.py b/tests/test_wallet.py index de0e0ace9c45..861755ed184d 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -27,6 +27,7 @@ @unittest.skipIf(TEST_NETWORK != 'regtest', "Test relies on a number of example addresses valid only in regtest") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't withdraw to non-wallet address") def test_withdraw(node_factory, bitcoind): amount = 1000000 # Don't get any funds from previous runs. @@ -815,6 +816,7 @@ def test_psbt_version(node_factory, bitcoind, chainparams): @unittest.skipIf(TEST_NETWORK == 'liquid-regtest', 'Core/Elements need joinpsbt support for v2') +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't withdraw to non-wallet address") def test_sign_and_send_psbt(node_factory, bitcoind, chainparams): """ Tests for the sign + send psbt RPCs @@ -1023,6 +1025,7 @@ def test_sign_and_send_psbt(node_factory, bitcoind, chainparams): check_coin_moves(l1, 'wallet', wallet_coin_mvts, chainparams) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't withdraw to non-wallet address") def test_txsend(node_factory, bitcoind, chainparams): amount = 1000000 l1 = node_factory.get_node(random_hsm=True) @@ -1145,6 +1148,7 @@ def write_all(fd, bytestr): @unittest.skipIf(VALGRIND, "It does not play well with prompt and key derivation.") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't support hsm_secret file") def test_hsm_secret_encryption(node_factory): l1 = node_factory.get_node(may_fail=True) # May fail when started without key password = "reckful&é🍕\n" @@ -1208,6 +1212,7 @@ def __init__(self, directory, *args): @unittest.skipIf(VALGRIND, "It does not play well with prompt and key derivation.") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't support hsm_secret file") def test_hsmtool_secret_decryption(node_factory): l1 = node_factory.get_node() password = "reckless123#{ù}\n" @@ -1350,6 +1355,7 @@ def test_hsmtool_dump_descriptors(node_factory, bitcoind): assert len(bitcoind.rpc.listunspent(1, 1, [addr])) == 1 +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't support generatehsm") def test_hsmtool_generatehsm(node_factory): l1 = node_factory.get_node(start=False) hsm_path = os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, @@ -1485,6 +1491,7 @@ def test_withdraw_nlocktime_fuzz(node_factory, bitcoind): raise Exception("No transaction with fuzzed nLockTime !") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't withdraw to non-wallet address") def test_multiwithdraw_simple(node_factory, bitcoind, chainparams): """ Test simple multiwithdraw usage. @@ -1586,6 +1593,7 @@ def test_repro_4258(node_factory, bitcoind): @unittest.skipIf(TEST_NETWORK == 'liquid-regtest', "Uses regtest addresses") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't withdraw to non-wallet address") def test_withdraw_bech32m(node_factory, bitcoind): l1 = node_factory.get_node() l1.fundwallet(10000000) From faff690d646ae431c140bddede3ad52259c5a74f Mon Sep 17 00:00:00 2001 From: Jules Comte <36807746+jules23@users.noreply.github.com> Date: Tue, 1 Mar 2022 17:29:03 -0500 Subject: [PATCH 05/53] Update install notes --- contrib/remote_hsmd/NOTES.md | 49 ++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/contrib/remote_hsmd/NOTES.md b/contrib/remote_hsmd/NOTES.md index 6c47bf8e530f..8ec317dd81c1 100644 --- a/contrib/remote_hsmd/NOTES.md +++ b/contrib/remote_hsmd/NOTES.md @@ -1,15 +1,6 @@ c-lightning ---------------------------------------------------------------- -Setup - - # Add an "upstream" reference and fetch all tags (needed for pyln-{proto,client,testing}) - git remote add upstream git@github.com:ElementsProject/lightning.git - git fetch upstream --tags - - (cd contrib/remote_hsmd && \ - ln -s ../../../validating-lightning-signer/lightning-signer-server/src/server/remotesigner.proto) - Additional Dependencies (needed after applying steps in `doc/INSTALL`): On Ubuntu: @@ -20,22 +11,42 @@ On Fedora: sudo dnf install -y grpc-devel grpc-plugins -Building +Python packages: + + pip3 install --user base58 bitstring secp256k1 mrkd + + # In c-lightning root, remote-hsmd branch: + pip3 install --user -r requirements.txt + + # Temporarily downgrade markupsafe to avoid breaking dependencies + pip3 install --user markupsafe==2.0.1 +Validating Lightning Signer: + + # In parent directory of c-lightning root + git clone git@gitlab.com:lightning-signer/validating-lightning-signer.git + cargo build --features log_pretty_print,debug_enforcement_state + +C-Lightning: + + # Add an "upstream" reference and fetch all tags (needed for pyln-{proto,client,testing}) + # Use the HTTPS pointer if you have authentication issues with git + git remote add upstream git@github.com:ElementsProject/lightning.git + git fetch upstream --tags + + (cd contrib/remote_hsmd && \ + ln -s ../../../validating-lightning-signer/lightning-signer-server/src/server/remotesigner.proto) + # Then do ls -alt contrib/remote_hsmd/remotesigner.proto and make sure the link is valid + + # Build c-lightning make distclean ./configure --enable-developer make -Build libsecp256k1 with `./configure --enable-module-recovery`, see -https://github.com/golemfactory/golem/issues/2168 for background. - - pip3 install --user base58 - pip3 install --user bitstring - pip3 install --user secp256k1 - pip3 install --user mrkd +Tests: - # in c-lightning root: - pip3 install --user -r requirements.txt + # Run this single test to check that everything is set up correctly: + ./contrib/remote_hsmd/scripts/run-one-test tests/test_pay.py::test_pay_no_secret Run all of the integration tests: From be80196a420b1ba3e7343684909bb2858b689cdd Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Wed, 2 Mar 2022 14:54:30 -0800 Subject: [PATCH 06/53] Improved basic setup instructions --- contrib/remote_hsmd/NOTES.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contrib/remote_hsmd/NOTES.md b/contrib/remote_hsmd/NOTES.md index 8ec317dd81c1..fd015896f5e7 100644 --- a/contrib/remote_hsmd/NOTES.md +++ b/contrib/remote_hsmd/NOTES.md @@ -1,7 +1,10 @@ c-lightning ---------------------------------------------------------------- -Additional Dependencies (needed after applying steps in `doc/INSTALL`): +Basic Setup: + +1. Follow instructions in `doc/INSTALL.md` +2. Follow instructions in the "Build and Development" section in `doc/HACKING.md` On Ubuntu: From d4cfa1bca226d9bb8baf915577600e18c2e4cc42 Mon Sep 17 00:00:00 2001 From: Jules Comte <36807746+jules23@users.noreply.github.com> Date: Mon, 7 Mar 2022 15:22:20 -0500 Subject: [PATCH 07/53] rearrange order of steps in NOTES.md --- contrib/remote_hsmd/NOTES.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/contrib/remote_hsmd/NOTES.md b/contrib/remote_hsmd/NOTES.md index fd015896f5e7..e804cfb661df 100644 --- a/contrib/remote_hsmd/NOTES.md +++ b/contrib/remote_hsmd/NOTES.md @@ -5,6 +5,7 @@ Basic Setup: 1. Follow instructions in `doc/INSTALL.md` 2. Follow instructions in the "Build and Development" section in `doc/HACKING.md` +3. Make sure you have cloned this repository On Ubuntu: @@ -14,16 +15,6 @@ On Fedora: sudo dnf install -y grpc-devel grpc-plugins -Python packages: - - pip3 install --user base58 bitstring secp256k1 mrkd - - # In c-lightning root, remote-hsmd branch: - pip3 install --user -r requirements.txt - - # Temporarily downgrade markupsafe to avoid breaking dependencies - pip3 install --user markupsafe==2.0.1 - Validating Lightning Signer: # In parent directory of c-lightning root @@ -32,7 +23,7 @@ Validating Lightning Signer: C-Lightning: - # Add an "upstream" reference and fetch all tags (needed for pyln-{proto,client,testing}) + # Standing in c-lightning root, add an "upstream" reference and fetch all tags (needed for pyln-{proto,client,testing}) # Use the HTTPS pointer if you have authentication issues with git git remote add upstream git@github.com:ElementsProject/lightning.git git fetch upstream --tags @@ -41,6 +32,15 @@ C-Lightning: ln -s ../../../validating-lightning-signer/lightning-signer-server/src/server/remotesigner.proto) # Then do ls -alt contrib/remote_hsmd/remotesigner.proto and make sure the link is valid + # Then install some python dependencies + pip3 install --user base58 bitstring secp256k1 mrkd + + # In c-lightning root, remote-hsmd branch: + pip3 install --user -r requirements.txt + + # Temporarily downgrade markupsafe to avoid breaking dependencies + pip3 install --user markupsafe==2.0.1 + # Build c-lightning make distclean ./configure --enable-developer From be7675d03c280cd57f9aaad6f2bbc98d20f1899a Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Wed, 16 Mar 2022 12:33:07 -0700 Subject: [PATCH 08/53] fixup: Rename formal parameter name to avoid C++ reserved word. --- common/utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/utils.h b/common/utils.h index 2aadec5fc3cf..8fdcfc03619d 100644 --- a/common/utils.h +++ b/common/utils.h @@ -165,6 +165,6 @@ extern const tal_t *wally_tal_ctx; /* Like mkstemp but resolves template relative to $TMPDIR (or /tmp if unset). * Returns created temporary path name at *created if successful. */ -int tmpdir_mkstemp(const tal_t *ctx, const char *template TAKES, char **created); +int tmpdir_mkstemp(const tal_t *ctx, const char *tmplt TAKES, char **created); #endif /* LIGHTNING_COMMON_UTILS_H */ From 8d58ccff4bc5a9dda28c8ea6ccd0712ce14f864d Mon Sep 17 00:00:00 2001 From: Jules Comte Date: Thu, 17 Mar 2022 18:30:14 -0400 Subject: [PATCH 09/53] doc: update remote hsmd notes --- contrib/remote_hsmd/NOTES.md | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/contrib/remote_hsmd/NOTES.md b/contrib/remote_hsmd/NOTES.md index e804cfb661df..3a5ba609fd5c 100644 --- a/contrib/remote_hsmd/NOTES.md +++ b/contrib/remote_hsmd/NOTES.md @@ -3,8 +3,8 @@ c-lightning Basic Setup: -1. Follow instructions in `doc/INSTALL.md` -2. Follow instructions in the "Build and Development" section in `doc/HACKING.md` +1. Follow instructions in [`doc/INSTALL.md`](../../doc/INSTALL.md) +2. Follow instructions in the "Build and Development" section in [`doc/HACKING.md`](../../doc/HACKING.md) 3. Make sure you have cloned this repository On Ubuntu: @@ -19,7 +19,7 @@ Validating Lightning Signer: # In parent directory of c-lightning root git clone git@gitlab.com:lightning-signer/validating-lightning-signer.git - cargo build --features log_pretty_print,debug_enforcement_state + cargo build C-Lightning: @@ -30,16 +30,8 @@ C-Lightning: (cd contrib/remote_hsmd && \ ln -s ../../../validating-lightning-signer/lightning-signer-server/src/server/remotesigner.proto) - # Then do ls -alt contrib/remote_hsmd/remotesigner.proto and make sure the link is valid - - # Then install some python dependencies - pip3 install --user base58 bitstring secp256k1 mrkd - - # In c-lightning root, remote-hsmd branch: - pip3 install --user -r requirements.txt - - # Temporarily downgrade markupsafe to avoid breaking dependencies - pip3 install --user markupsafe==2.0.1 + # Then make sure the link is valid + if [ -f contrib/remote_hsmd/remotesigner.proto ]; then echo "Symbolic link successful"; else echo "Symbolic link failed"; fi # Build c-lightning make distclean From 3d89a6bd6ea91ef80b488704c30f691301ce9b05 Mon Sep 17 00:00:00 2001 From: Devrandom Date: Tue, 22 Mar 2022 10:23:28 +0100 Subject: [PATCH 10/53] Move proxy to another repo --- contrib/remote_hsmd/.gitignore | 5 - contrib/remote_hsmd/Makefile | 124 -- contrib/remote_hsmd/NOTES.md | 119 - contrib/remote_hsmd/TESTING_ALLOWLIST | 41 - contrib/remote_hsmd/TODO.md | 44 - contrib/remote_hsmd/capabilities.h | 12 - contrib/remote_hsmd/dump.cc | 548 ----- contrib/remote_hsmd/dump.hpp | 48 - contrib/remote_hsmd/hsmd.c | 1946 ----------------- contrib/remote_hsmd/proxy.cc | 1684 -------------- contrib/remote_hsmd/proxy.hpp | 231 -- contrib/remote_hsmd/scripts/cleanup | 10 - .../remote_hsmd/scripts/rerun-failed-tests | 23 - contrib/remote_hsmd/scripts/run-all-tests | 17 - contrib/remote_hsmd/scripts/run-one-test | 19 - 15 files changed, 4871 deletions(-) delete mode 100644 contrib/remote_hsmd/.gitignore delete mode 100644 contrib/remote_hsmd/Makefile delete mode 100644 contrib/remote_hsmd/NOTES.md delete mode 100644 contrib/remote_hsmd/TESTING_ALLOWLIST delete mode 100644 contrib/remote_hsmd/TODO.md delete mode 100644 contrib/remote_hsmd/capabilities.h delete mode 100644 contrib/remote_hsmd/dump.cc delete mode 100644 contrib/remote_hsmd/dump.hpp delete mode 100644 contrib/remote_hsmd/hsmd.c delete mode 100644 contrib/remote_hsmd/proxy.cc delete mode 100644 contrib/remote_hsmd/proxy.hpp delete mode 100755 contrib/remote_hsmd/scripts/cleanup delete mode 100755 contrib/remote_hsmd/scripts/rerun-failed-tests delete mode 100755 contrib/remote_hsmd/scripts/run-all-tests delete mode 100755 contrib/remote_hsmd/scripts/run-one-test diff --git a/contrib/remote_hsmd/.gitignore b/contrib/remote_hsmd/.gitignore deleted file mode 100644 index 48222a90f3d6..000000000000 --- a/contrib/remote_hsmd/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -/remotesigner.grpc.pb.cc -/remotesigner.grpc.pb.h -/remotesigner.pb.cc -/remotesigner.pb.h -/remotesigner.proto diff --git a/contrib/remote_hsmd/Makefile b/contrib/remote_hsmd/Makefile deleted file mode 100644 index f7ef5483db3a..000000000000 --- a/contrib/remote_hsmd/Makefile +++ /dev/null @@ -1,124 +0,0 @@ -#! /usr/bin/make - -# Designed to be run at the top -rmthsmd-wrongdir: - $(MAKE) -C ../.. lightningd/hsm-all - -default: rmthsmd-all - -LIGHTNINGD_RMTHSM_SRC := contrib/remote_hsmd/hsmd.c \ - contrib/remote_hsmd/remotesigner.pb.cc \ - contrib/remote_hsmd/remotesigner.grpc.pb.cc \ - contrib/remote_hsmd/dump.cc \ - contrib/remote_hsmd/proxy.cc -LIGHTNINGD_RMTHSM_HEADERS := contrib/remote_hsmd/remotesigner.pb.h \ - contrib/remote_hsmd/remotesigner.grpc.pb.h -LIGHTNINGD_RMTHSM_CCOBJS := $(LIGHTNINGD_RMTHSM_SRC:.cc=.o) -LIGHTNINGD_RMTHSM_OBJS := $(LIGHTNINGD_RMTHSM_CCOBJS:.c=.o) - -HOST_SYSTEM = $(shell uname | cut -f 1 -d_) -SYSTEM ?= $(HOST_SYSTEM) -CXX = g++ -CXXFLAGS += -std=c++11 \ - -I. \ - -Iccan \ - -Iexternal/libwally-core/include \ - -Iexternal/libwally-core/src/secp256k1/include \ - -g -PROTOC = protoc -GRPC_CPP_PLUGIN = grpc_cpp_plugin -GRPC_CPP_PLUGIN_PATH ?= `which $(GRPC_CPP_PLUGIN)` -PROTOS_PATH = contrib/remote_hsmd - -# Common source we use. -RMTHSMD_COMMON_OBJS := \ - hsmd/hsmd_wiregen.o \ - common/amount.o \ - common/autodata.o \ - common/base32.o \ - common/bigsize.o \ - common/bip32.o \ - common/channel_id.o \ - common/channel_type.o \ - common/daemon.o \ - common/daemon_conn.o \ - common/derive_basepoints.o \ - common/features.o \ - common/status_wire.o \ - common/status_wiregen.o \ - common/hash_u5.o \ - common/htlc_state.o \ - common/htlc_wire.o \ - common/key_derive.o \ - common/memleak.o \ - common/msg_queue.o \ - common/node_id.o \ - common/onionreply.o \ - common/permute_tx.o \ - common/pseudorand.o \ - common/setup.o \ - common/status.o \ - common/status_wire.o \ - common/status_wiregen.o \ - common/subdaemon.o \ - common/type_to_string.o \ - common/utils.o \ - common/utxo.o \ - common/version.o \ - common/wireaddr.o - -# For checking -LIGHTNINGD_RMTHSM_ALLSRC_NOGEN := $(filter-out contrib/remote_hsmd/remotesigner.%, $(LIGHTNINGD_RMTHSM_SRC) $(LIGHTNINGD_RMTHSM_SRC)) -LIGHTNINGD_RMTHSM_ALLHEADERS_NOGEN := $(filter-out contrib/remote_hsmd/remotesigner.%, $(LIGHTNINGD_RMTHSM_HEADERS)) - -$(LIGHTNINGD_RMTHSM_OBJS): $(LIGHTNINGD_HEADERS) - -# Make sure these depend on everything. -ALL_OBJS += $(LIGHTNINGD_RMTHSM_OBJS) -ALL_PROGRAMS += lightningd/remote_hsmd -ALL_GEN_HEADERS += contrib/remote_hsmd/remotesigner.pb.h contrib/remote_hsmd/remotesigner.grpc.pb.h - -rmthsmd-all: lightningd/remote_hsmd - -lightningd/remote_hsmd: $(LIGHTNINGD_RMTHSM_OBJS) $(LIGHTNINGD_LIB_OBJS) $(RMTHSMD_COMMON_OBJS) $(BITCOIN_OBJS) $(WIRE_OBJS) - -contrib/remote_hsmd/remotesigner.pb.o: $(ALL_GEN_HEADERS) - -contrib/remote_hsmd/dump.o contrib/remote_hsmd/proxy.o: $(ALL_GEN_HEADERS) - -contrib/remote_hsmd/remotesigner.grpc.pb.o contrib/remote_hsmd/remotesigner.pb.o contrib/remote_hsmd/proxy.o contrib/remote_hsmd/dump.o : CPPFLAGS += $(CMNFLAGS) `pkg-config --cflags protobuf grpc` -lightningd/remote_hsmd: LDLIBS += -L/usr/local/lib \ - `pkg-config --libs protobuf grpc++`\ - -pthread\ - -Wl,--no-as-needed -lgrpc++_reflection -Wl,--as-needed\ - -ldl -lightningd/remote_hsmd: LDLIBS += -lstdc++ - -check-source: $(LIGHTNINGD_RMTHSM_ALLSRC_NOGEN:%=check-src-include-order/%) $(LIGHTNINGD_RMTHSM_ALLHEADERS_NOGEN:%=check-hdr-include-order/%) -check-source-bolt: $(LIGHTNINGD_RMTHSM_SRC:%=bolt-check/%) - -check-whitespace: $(LIGHTNINGD_RMTHSM_ALLSRC_NOGEN:%=check-whitespace/%) $(LIGHTNINGD_RMTHSM_ALLHEADERS_NOGEN:%=check-whitespace/%) - -clean: lightningd/rmthsm-clean - -lightningd/rmthsm-clean: - $(RM) $(LIGHTNINGD_RMTHSM_OBJS) - $(RM) contrib/remote_hsmd/*.pb.cc contrib/remote_hsmd/*.pb.h - -.PRECIOUS: %.grpc.pb.cc -%.grpc.pb.cc: %.proto - $(PROTOC) -I $(PROTOS_PATH) --grpc_out=contrib/remote_hsmd --plugin=protoc-gen-grpc=$(GRPC_CPP_PLUGIN_PATH) $< - -.PRECIOUS: %.pb.cc -%.pb.cc: %.proto - $(PROTOC) -I $(PROTOS_PATH) --cpp_out=contrib/remote_hsmd $< - -.PRECIOUS: %.grpc.pb.h -%.grpc.pb.h: %.proto - $(PROTOC) -I $(PROTOS_PATH) --grpc_out=contrib/remote_hsmd --plugin=protoc-gen-grpc=$(GRPC_CPP_PLUGIN_PATH) $< - -.PRECIOUS: %.pb.h -%.pb.h: %.proto - $(PROTOC) -I $(PROTOS_PATH) --cpp_out=contrib/remote_hsmd $< - --include contrib/remote_hsmd/test/Makefile diff --git a/contrib/remote_hsmd/NOTES.md b/contrib/remote_hsmd/NOTES.md deleted file mode 100644 index 3a5ba609fd5c..000000000000 --- a/contrib/remote_hsmd/NOTES.md +++ /dev/null @@ -1,119 +0,0 @@ -c-lightning ----------------------------------------------------------------- - -Basic Setup: - -1. Follow instructions in [`doc/INSTALL.md`](../../doc/INSTALL.md) -2. Follow instructions in the "Build and Development" section in [`doc/HACKING.md`](../../doc/HACKING.md) -3. Make sure you have cloned this repository - -On Ubuntu: - - sudo apt-get install -y libgrpc-dev libgrpc++-dev protobuf-compiler-grpc - -On Fedora: - - sudo dnf install -y grpc-devel grpc-plugins - -Validating Lightning Signer: - - # In parent directory of c-lightning root - git clone git@gitlab.com:lightning-signer/validating-lightning-signer.git - cargo build - -C-Lightning: - - # Standing in c-lightning root, add an "upstream" reference and fetch all tags (needed for pyln-{proto,client,testing}) - # Use the HTTPS pointer if you have authentication issues with git - git remote add upstream git@github.com:ElementsProject/lightning.git - git fetch upstream --tags - - (cd contrib/remote_hsmd && \ - ln -s ../../../validating-lightning-signer/lightning-signer-server/src/server/remotesigner.proto) - # Then make sure the link is valid - if [ -f contrib/remote_hsmd/remotesigner.proto ]; then echo "Symbolic link successful"; else echo "Symbolic link failed"; fi - - # Build c-lightning - make distclean - ./configure --enable-developer - make - -Tests: - - # Run this single test to check that everything is set up correctly: - ./contrib/remote_hsmd/scripts/run-one-test tests/test_pay.py::test_pay_no_secret - -Run all of the integration tests: - - ./contrib/remote_hsmd/scripts/run-all-tests |& tee log - -Run a single test: - - ./contrib/remote_hsmd/scripts/run-one-test $THETEST |& tee log - -Re-run failures from prior run: - - ./contrib/remote_hsmd/scripts/rerun-failed-tests < log |& tee log2 - -To run tests using anchors, rebuild w/ experimental features: - - make distclean - ./configure --enable-developer --enable-experimental-features - make - - ./contrib/remote_hsmd/scripts/run-all-tests |& tee log - -Some popular tests: - - # sign-invoice, handle-sign-remote-htlc-tx - export THETEST=tests/test_connection.py::test_balance - export THETEST=tests/test_pay.py::test_sendpay - export THETEST=tests/test_pay.py::test_pay - - # sign-local-htlc-tx - export THETEST=tests/test_closing.py::test_onchain_different_fees - - # sign-remote-htlc-to-us - export THETEST=tests/test_closing.py::test_onchain_feechange - export THETEST=tests/test_closing.py::test_onchain_all_dust - export THETEST=tests/test_closing.py::test_permfail_new_commit - - # sign-delayed-payment-to-us - export THETEST=tests/test_closing.py::test_onchain_multihtlc_our_unilateral - export THETEST=tests/test_closing.py::test_onchain_multihtlc_their_unilateral - export THETEST=tests/test_closing.py::test_permfail_htlc_in - export THETEST=tests/test_closing.py::test_permfail_htlc_out - - # sign-penalty-to-us - export THETEST=tests/test_closing.py::test_penalty_inhtlc - export THETEST=tests/test_closing.py::test_penalty_outhtlc - export THETEST=tests/test_closing.py::test_closing - - # sign-mutual-close - export THETEST=tests/test_closing.py::test_closing - - # check-future-secret - export THETEST=tests/test_connection.py::test_dataloss_protection - - # sign-message - export THETEST=tests/test_misc.py::test_signmessage - - # sign-channel-announcement - export THETEST=tests/test_closing.py::test_closing_different_fees - - # P2SH_P2WPKH - export THETEST=tests/test_closing.py::test_onchain_first_commit - export THETEST=tests/test_connection.py::test_disconnect_funder - export THETEST=tests/test_connection.py::test_disconnect_fundee - export THETEST=tests/test_connection.py::test_reconnect_signed - export THETEST=tests/test_connection.py::test_reconnect_openingd - export THETEST=tests/test_connection.py::test_shutdown_awaiting_lockin - - # unilateral_close_info option_static_remotekey - export THETEST=tests/test_connection.py::test_fee_limits - export THETEST=tests/test_closing.py::test_option_upfront_shutdown_script - -validating-lightning-signer ----------------------------------------------------------------- - - cargo run --bin server -- --no-persist --test-mode |& tee log3 diff --git a/contrib/remote_hsmd/TESTING_ALLOWLIST b/contrib/remote_hsmd/TESTING_ALLOWLIST deleted file mode 100644 index ca919f4ba7f8..000000000000 --- a/contrib/remote_hsmd/TESTING_ALLOWLIST +++ /dev/null @@ -1,41 +0,0 @@ -bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg -bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080 -bcrt1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qzf4jry -bcrt1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k0ylj56 -bcrt1sw50qt2uwha -bcrt1zw508d6qejxtdg4y5r3zarvaryv2wuatf -bcrt1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvseswlauz7 -bcrt1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesyga46z -bcrt1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqc8gma6 -bcrt1pqqqqrldkrl -bcrt1pqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqwekeum -bcrt1sqqqq4wstyw -bcrt1sqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq8v496j -bcrt1zqqqq0u8uvu -bcrt1zqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqla7vv2 -bcrt1rqqqqtap6fa -bcrt1rqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsxpcm9 -bcrt1yqqqqh6ngj6 -bcrt1yqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq54w09p -bcrt19qqqqnm4whm -bcrt19qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmw3mjw -bcrt1xqqqqlclycc -bcrt1xqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq22ewzl -bcrt18qqqqmeezae -bcrt18qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq93x64s -bcrt1gqqqqwkjf8k -bcrt1gqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqz98fhh -bcrt1fqqqq2h50zh -bcrt1fqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqd7caqc -bcrt12qqqqx579d5 -bcrt12qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqu6sgsf -bcrt1tqqqqz4crg4 -bcrt1tqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnp0u8x -bcrt1vqqqq7j23nj -bcrt1vqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhjqtez -bcrt1dqqqq6nvhkn -bcrt1dqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqcfllwd -bcrt1wqqqqksxaes -bcrt1wqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfdh27u -bcrt10qqqqj3qmu3 -bcrt10qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqxkg7fn diff --git a/contrib/remote_hsmd/TODO.md b/contrib/remote_hsmd/TODO.md deleted file mode 100644 index 1b22735f129b..000000000000 --- a/contrib/remote_hsmd/TODO.md +++ /dev/null @@ -1,44 +0,0 @@ - -API Coverage ----------------------------------------------------------------- - -## Failing Tests - -# Frequently Intermittent -tests/test_connection.py::test_funding_cancel_race -tests/test_misc.py::test_bad_onion_immediate_peer - -## Proxy Scoreboard - -``` -COMPLETE proxy_stat proxy_handle_ecdh -COMPLETE proxy_stat proxy_handle_pass_client_hsmfd -COMPLETE proxy_stat proxy_handle_sign_remote_commitment_tx -COMPLETE proxy_stat proxy_handle_channel_update_sig -COMPLETE proxy_stat proxy_handle_sign_node_announcement -COMPLETE proxy_stat proxy_handle_sign_remote_htlc_tx -COMPLETE proxy_stat proxy_handle_sign_invoice -COMPLETE proxy_stat proxy_handle_get_channel_basepoints -COMPLETE proxy_stat proxy_handle_get_per_commitment_point -COMPLETE proxy_stat proxy_handle_sign_local_htlc_tx -COMPLETE proxy_stat proxy_handle_sign_remote_htlc_to_us -COMPLETE proxy_stat proxy_handle_sign_delayed_payment_to_us -COMPLETE proxy_stat proxy_handle_sign_penalty_to_us -COMPLETE proxy_stat proxy_handle_sign_commitment_tx -COMPLETE proxy_stat proxy_handle_sign_mutual_close_tx -COMPLETE proxy_stat proxy_handle_check_future_secret -COMPLETE proxy_stat proxy_handle_cannouncement_sig -COMPLETE proxy_stat proxy_handle_sign_message - -PARTIAL (-P2SH) proxy_stat proxy_handle_sign_withdrawal_tx - -MARSHALED proxy_stat proxy_init_hsm -``` - -Improvements ----------------------------------------------------------------- - -#### Remove contrib/remote_signer/hsm_wire.csv - -Generate gen_hsm_wire.{h,c} from c-lightning/hsmd/hsm_wire.csv instead. - diff --git a/contrib/remote_hsmd/capabilities.h b/contrib/remote_hsmd/capabilities.h deleted file mode 100644 index 3c139c26de70..000000000000 --- a/contrib/remote_hsmd/capabilities.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef LIGHTNING_CONTRIB_REMOTE_HSMD_CAPABILITIES_H -#define LIGHTNING_CONTRIB_REMOTE_HSMD_CAPABILITIES_H - -#define HSM_CAP_ECDH 1 -#define HSM_CAP_SIGN_GOSSIP 2 -#define HSM_CAP_SIGN_ONCHAIN_TX 4 -#define HSM_CAP_COMMITMENT_POINT 8 -#define HSM_CAP_SIGN_REMOTE_TX 16 -#define HSM_CAP_SIGN_CLOSING_TX 32 - -#define HSM_CAP_MASTER 1024 -#endif /* LIGHTNING_CONTRIB_REMOTE_HSMD_CAPABILITIES_H */ diff --git a/contrib/remote_hsmd/dump.cc b/contrib/remote_hsmd/dump.cc deleted file mode 100644 index 2e29b10b90e3..000000000000 --- a/contrib/remote_hsmd/dump.cc +++ /dev/null @@ -1,548 +0,0 @@ -#include "contrib/remote_hsmd/dump.hpp" - -extern "C" { -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -} -#include -#include -extern "C" { -#include -#include -} - - -using std::ostringstream; -using std::string; - -bool pubkey_is_compressed(const unsigned char pubkey[EC_PUBLIC_KEY_UNCOMPRESSED_LEN]) { - return pubkey[0] == 0x02 || pubkey[0] == 0x03; -} - -/* dump an optional wallet index */ -string dump_optional_wallet_index(u32 *optional_wallet_index) -{ - ostringstream ostrm; - if (optional_wallet_index == NULL) { - ostrm << "NONE"; - } else { - ostrm << *optional_wallet_index; - } - return ostrm.str(); -} - -/* type_to_string has issues in the C++ environment, use this to - dump binary data as hex instead. */ -string dump_hex(const void *vptr, size_t sz) -{ - static const char hex[] = "0123456789abcdef"; - ostringstream ostrm; - ostrm << '"'; - uint8_t const * ptr = (uint8_t const *) vptr; - for (size_t ii = 0; ii < sz; ++ii) { - ostrm << hex[(*ptr) >> 4] - << hex[(*ptr) & 0xf]; - ++ptr; - } - ostrm << '"'; - return ostrm.str(); -} - -string dump_bitcoin_txid(const struct bitcoin_txid *txid) -{ - // reverse the bytes, a-la bitcoind - struct sha256_double rev = txid->shad; - reverse_bytes(rev.sha.u.u8, sizeof(rev.sha.u.u8)); - return dump_hex(rev.sha.u.u8, sizeof(rev.sha.u.u8)); -} - -string dump_bitcoin_signature(const struct bitcoin_signature *sp) -{ - ostringstream ostrm; - ostrm << "{ " - << "\"sighash_type\":" << int(sp->sighash_type) - << ", \"s\":" - << dump_secp256k1_ecdsa_signature(&sp->s) - << " }"; - return ostrm.str(); -} - -string dump_htlc_signatures(const struct bitcoin_signature *sps) -{ - ostringstream ostrm; - ostrm << "["; - for (size_t input_ndx = 0; input_ndx < tal_count(sps); ++input_ndx) { - if (input_ndx != 0) - ostrm << ", "; - ostrm << dump_bitcoin_signature(&sps[input_ndx]); - } - ostrm << "]"; - return ostrm.str(); -} - -string dump_secp256k1_ecdsa_signature(const secp256k1_ecdsa_signature *sp) -{ - return dump_hex(sp->data, sizeof(sp->data)); -} - -string dump_secp256k1_ecdsa_recoverable_signature( - const secp256k1_ecdsa_recoverable_signature *sp) -{ - return dump_hex(sp->data, sizeof(sp->data)); -} - -string dump_secret(const struct secret *sp) -{ - return dump_hex(sp->data, sizeof(sp->data)); -} - -string dump_node_id(const struct node_id *pp) -{ - return dump_hex(pp->k, sizeof(pp->k)); -} - -string dump_pubkey(const struct pubkey *kp) -{ - return dump_hex(kp->pubkey.data, sizeof(kp->pubkey.data)); -} - -string dump_ext_pubkey(const struct ext_key *xp) -{ - char *out; - tal_wally_start(); - int rv = bip32_key_to_base58(xp, BIP32_FLAG_KEY_PUBLIC, &out); - tal_wally_end(NULL); - assert(rv == WALLY_OK); - string retval(out); - wally_free_string(out); - return retval; -} - -string dump_witnesses(const u8 ***wp) -{ - ostringstream ostrm; - ostrm << "["; - for (size_t input_ndx = 0; input_ndx < tal_count(wp); ++input_ndx) { - if (input_ndx != 0) - ostrm << ", "; - ostrm << "["; - u8 const *sig = wp[input_ndx][0]; - ostrm << dump_hex(sig, tal_count(sig)); - ostrm << ", "; - u8 const *pubkey = wp[input_ndx][1]; - ostrm << dump_hex(pubkey, tal_count(pubkey)); - ostrm << "]"; - } - ostrm << "]"; - return ostrm.str(); -} - -string dump_basepoints(const struct basepoints *bp) -{ - ostringstream ostrm; - ostrm << "{ "; - ostrm << "\"revocation\":" << dump_pubkey(&bp->revocation); - ostrm << ", \"payment\":" << dump_pubkey(&bp->payment); - ostrm << ", \"htlc\":" << dump_pubkey(&bp->htlc); - ostrm << ", \"delayed_payment\":" << dump_pubkey(&bp->delayed_payment); - ostrm << " }"; - return ostrm.str(); -} - -string dump_unilateral_close_info(const struct unilateral_close_info *ip) -{ - ostringstream ostrm; - ostrm << "{ "; - ostrm << "\"channel_id\":" << ip->channel_id; - ostrm << ", \"peer_id\":" << dump_node_id(&ip->peer_id); - ostrm << ", \"commitment_point\":" << - (ip->commitment_point ? dump_pubkey(ip->commitment_point) : - "\"\""); - ostrm << " }"; - return ostrm.str(); -} - -string dump_utxo(const struct utxo *in) -{ - ostringstream ostrm; - ostrm << "{ "; - ostrm << "\"txid\":" << dump_bitcoin_txid(&in->outpoint.txid); - ostrm << ", \"outnum\":" << in->outpoint.n; - ostrm << ", \"amount\":" << in->amount.satoshis; - ostrm << ", \"keyindex\":" << in->keyindex; - ostrm << ", \"is_p2sh\":" << in->is_p2sh; - ostrm << ", \"close_info\":" << - (in->close_info ? - dump_unilateral_close_info(in->close_info) : - "\"\""); - ostrm << " }"; - return ostrm.str(); -} - -string dump_utxos(const struct utxo **utxos) -{ - ostringstream ostrm; - ostrm << "["; - for (size_t ii = 0; ii < tal_count(utxos); ii++) { - if (ii != 0) - ostrm << ","; - ostrm << dump_utxo(utxos[ii]); - } - ostrm << "]"; - return ostrm.str(); -} - -string dump_bitcoin_tx_output(const struct bitcoin_tx_output *op) -{ - ostringstream ostrm; - ostrm << "{ "; - ostrm << "\"amount\":" << op->amount.satoshis; - ostrm << ", \"script\":" << - (op->script ? dump_hex(op->script, tal_count(op->script)) : "\"\""); - ostrm << " }"; - return ostrm.str(); -} - -string dump_bitcoin_tx_outputs(const struct bitcoin_tx_output **outputs) -{ - ostringstream ostrm; - ostrm << "["; - for (size_t ii = 0; ii < tal_count(outputs); ii++) { - if (ii != 0) - ostrm << ","; - ostrm << dump_bitcoin_tx_output(outputs[ii]); - } - ostrm << "]"; - return ostrm.str(); -} - -string dump_wally_tx_witness_stack(const struct wally_tx_witness_stack *sp) -{ - if (sp == NULL) - return "[]"; - ostringstream ostrm; - ostrm << "["; - for (size_t ii = 0; ii < sp->num_items; ii++) { - if (ii != 0) - ostrm << ","; - ostrm << dump_hex(sp->items[ii].witness, - sp->items[ii].witness_len); - } - ostrm << "]"; - return ostrm.str(); -} - -string dump_wally_keypath_item(const struct wally_map_item *ip) -{ - size_t npath = (ip->value_len - BIP32_KEY_FINGERPRINT_LEN) / sizeof(uint32_t); - uint32_t * path = (uint32_t *) (ip->value + BIP32_KEY_FINGERPRINT_LEN); - ostringstream ostrm; - ostrm << "{ "; - ostrm << "\"pubkey\":" << dump_hex(ip->key, ip->key_len); - ostrm << ", \"origin\":{ "; - ostrm << " \"fingerprint\":" - << dump_hex(ip->value, BIP32_KEY_FINGERPRINT_LEN); - ostrm << ", \"path\":[ "; - for (size_t ii = 0; ii < npath; ++ii) { - if (ii != 0) - ostrm << ","; - ostrm << le32_to_cpu(path[ii]); - } - ostrm << " ]"; - ostrm << " }"; - ostrm << " }"; - return ostrm.str(); -} - -string dump_wally_keypath_map(const struct wally_map *mp) -{ - ostringstream ostrm; - ostrm << "["; - if (mp) { - for (size_t ii = 0; ii < mp->num_items; ii++) { - if (ii != 0) - ostrm << ","; - ostrm << dump_wally_keypath_item(&mp->items[ii]); - } - } - ostrm << "]"; - return ostrm.str(); -} - -string dump_wally_signatures_item(const struct wally_map_item *ip) -{ - ostringstream ostrm; - ostrm << "{ "; - ostrm << "\"pubkey\":" << dump_hex(ip->key, ip->key_len); - ostrm << ", \"sig\":" << dump_hex(ip->value, ip->value_len); - ostrm << " }"; - return ostrm.str(); -} - -string dump_wally_signatures_map(const struct wally_map *mp) -{ - ostringstream ostrm; - ostrm << "["; - if (mp) { - for (size_t ii = 0; ii < mp->num_items; ii++) { - if (ii != 0) - ostrm << ","; - ostrm << dump_wally_signatures_item(&mp->items[ii]); - } - } - ostrm << "]"; - return ostrm.str(); -} - -string dump_wally_unknowns_item(const struct wally_map_item *ip) -{ - ostringstream ostrm; - ostrm << "{ "; - ostrm << "\"key\":" << dump_hex(ip->key, ip->key_len); - ostrm << ", \"value\":" << dump_hex(ip->value, ip->value_len); - ostrm << " }"; - return ostrm.str(); -} - -string dump_wally_unknowns_map(const struct wally_map *mp) -{ - ostringstream ostrm; - ostrm << "["; - if (mp) { - for (size_t ii = 0; ii < mp->num_items; ii++) { - if (ii != 0) - ostrm << ","; - ostrm << dump_wally_unknowns_item(&mp->items[ii]); - } - } - ostrm << "]"; - return ostrm.str(); -} - -string dump_wally_tx_input(const struct wally_tx_input *in) -{ - ostringstream ostrm; - ostrm << "{ "; - ostrm << "\"txhash\":" << dump_hex(in->txhash, sizeof(in->txhash)); - ostrm << ", \"index\":" << in->index; - ostrm << ", \"sequence\":" << in->sequence; - ostrm << ", \"script\":" << - (in->script_len ? dump_hex(in->script, in->script_len) : - "\"\""); - ostrm << ", \"witness\":" << - (in->witness ? dump_wally_tx_witness_stack(in->witness) : - "\"\""); - ostrm << ", \"features\":" << int(in->features); - ostrm << " }"; - return ostrm.str(); -} - -string dump_wally_tx_inputs(const struct wally_tx_input *inputs, - size_t num_inputs) -{ - ostringstream ostrm; - ostrm << "["; - for (size_t ii = 0; ii < num_inputs; ii++) { - if (ii != 0) - ostrm << ","; - ostrm << dump_wally_tx_input(&inputs[ii]); - } - ostrm << "]"; - return ostrm.str(); -} - -string dump_wally_tx_output(const struct wally_tx_output *out) -{ - if (out == NULL) - return "{}"; - ostringstream ostrm; - ostrm << "{ "; - ostrm << "\"satoshi\":" << out->satoshi; - ostrm << ", \"script\":" << - (out->script_len ? dump_hex(out->script, out->script_len) : - "\"\""); - ostrm << ", \"features\":" << int(out->features); - ostrm << " }"; - return ostrm.str(); -} - -string dump_wally_tx_outputs(const struct wally_tx_output *outputs, - size_t num_outputs) -{ - ostringstream ostrm; - ostrm << "["; - for (size_t ii = 0; ii < num_outputs; ii++) { - if (ii != 0) - ostrm << ","; - ostrm << dump_wally_tx_output(&outputs[ii]); - } - ostrm << "]"; - return ostrm.str(); -} - -string dump_wally_tx(const struct wally_tx *wtx) -{ - if (wtx == NULL) - return "{}"; - ostringstream ostrm; - ostrm << "{ "; - ostrm << "\"version\":" << wtx->version; - ostrm << ", \"locktime\":" << wtx->locktime; - ostrm << ", \"inputs\":" << - dump_wally_tx_inputs(wtx->inputs, wtx->num_inputs); - ostrm << ", \"inputs_allocation_len\":" << wtx->inputs_allocation_len; - ostrm << ", \"outputs\":" << - dump_wally_tx_outputs(wtx->outputs, wtx->num_outputs); - ostrm << ", \"outputs_allocation_len\":" << wtx->outputs_allocation_len; - ostrm << " }"; - return ostrm.str(); -} - -string dump_wally_psbt_input(const struct wally_psbt_input *in) -{ - ostringstream ostrm; - ostrm << "{ "; - ostrm << "\"utxo\":" << dump_wally_tx(in->utxo); - ostrm << ", \"witness_utxo\":" << dump_wally_tx_output(in->witness_utxo); - ostrm << ", \"redeem_script\":" << dump_hex(in->redeem_script, - in->redeem_script_len); - ostrm << ", \"witness_script\":" << dump_hex(in->witness_script, - in->witness_script_len); - ostrm << ", \"final_scriptsig\":" << dump_hex(in->final_scriptsig, - in->final_scriptsig_len); - ostrm << ", \"final_witness\":" - << dump_wally_tx_witness_stack(in->final_witness); - ostrm << ", \"keypaths\":" << dump_wally_keypath_map(&in->keypaths); - ostrm << ", \"signatures\":" - << dump_wally_signatures_map(&in->signatures); - ostrm << ", \"unknowns\":" << dump_wally_unknowns_map(&in->unknowns); - ostrm << ", \"sighash\":" << in->sighash; - ostrm << " }"; - return ostrm.str(); -} - -string dump_wally_psbt_inputs(const struct wally_psbt_input *inputs, - size_t num_inputs) -{ - ostringstream ostrm; - ostrm << "["; - for (size_t ii = 0; ii < num_inputs; ii++) { - if (ii != 0) - ostrm << ","; - ostrm << dump_wally_psbt_input(&inputs[ii]); - } - ostrm << "]"; - return ostrm.str(); -} - -string dump_wally_psbt_output(const struct wally_psbt_output *out) -{ - ostringstream ostrm; - ostrm << "{ "; - ostrm << "\"redeem_script\":" << dump_hex(out->redeem_script, - out->redeem_script_len); - ostrm << ", \"witness_script\":" << dump_hex(out->witness_script, - out->witness_script_len); - ostrm << ", \"keypaths\":" << dump_wally_keypath_map(&out->keypaths); - ostrm << ", \"unknowns\":" << dump_wally_unknowns_map(&out->unknowns); - ostrm << " }"; - return ostrm.str(); -} - -string dump_wally_psbt_outputs(const struct wally_psbt_output *outputs, - size_t num_outputs) -{ - ostringstream ostrm; - ostrm << "["; - for (size_t ii = 0; ii < num_outputs; ii++) { - if (ii != 0) - ostrm << ","; - ostrm << dump_wally_psbt_output(&outputs[ii]); - } - ostrm << "]"; - return ostrm.str(); -} - -string dump_wally_psbt(const struct wally_psbt *psbt) -{ - ostringstream ostrm; - ostrm << "{ "; - ostrm << "\"magic\":" << dump_hex(psbt->magic, sizeof(psbt->magic)); - ostrm << ", \"tx\":" << dump_wally_tx(psbt->tx); - ostrm << ", \"inputs\":" - << dump_wally_psbt_inputs(psbt->inputs, psbt->num_inputs); - ostrm << ", \"outputs\":" - << dump_wally_psbt_outputs(psbt->outputs, psbt->num_outputs); - ostrm << ", \"unknowns\":" << dump_wally_unknowns_map(&psbt->unknowns); - ostrm << ", \"version\":" << psbt->version; - ostrm << " }"; - return ostrm.str(); -} - -string dump_tx(const struct bitcoin_tx *tx) -{ - ostringstream ostrm; - ostrm << "{ "; - ostrm << "\"wtx\":" << dump_wally_tx(tx->wtx); - ostrm << ", \"psbt\":" << dump_wally_psbt(tx->psbt); - ostrm << " }"; - return ostrm.str(); -} - -string dump_rhashes(const struct sha256 *rhashes, size_t num_rhashes) -{ - ostringstream ostrm; - ostrm << "["; - for (size_t ii = 0; ii < num_rhashes; ii++) { - if (ii != 0) - ostrm << ","; - ostrm << dump_hex(&rhashes[ii], sizeof(rhashes[ii])); - } - ostrm << "]"; - return ostrm.str(); -} - -string dump_htlc(const struct simple_htlc *htlc) -{ - ostringstream ostrm; - ostrm << "{ " - << ", \"side\":" << htlc->side - << ", \"amount_msat\":" << htlc->amount.millisatoshis - << ", \"payment_hash\":" << dump_hex(&htlc->payment_hash, sizeof(htlc->payment_hash)) - << ", \"cltv_expiry\":" << htlc->cltv_expiry - << " }"; - return ostrm.str(); -} - -string dump_htlcs(const struct simple_htlc **htlc, size_t num_htlc) -{ - ostringstream ostrm; - ostrm << "["; - for (size_t ii = 0; ii < num_htlc; ii++) { - if (ii != 0) - ostrm << ","; - ostrm << dump_htlc(htlc[ii]); - } - ostrm << "]"; - return ostrm.str(); -} - -/* . Bitcoind represents hashes as little-endian for RPC. */ -void reverse_bytes(u8 *arr, size_t len) -{ - unsigned int i; - - for (i = 0; i < len / 2; i++) { - unsigned char tmp = arr[i]; - arr[i] = arr[len - 1 - i]; - arr[len - 1 - i] = tmp; - } -} diff --git a/contrib/remote_hsmd/dump.hpp b/contrib/remote_hsmd/dump.hpp deleted file mode 100644 index 3172847a126d..000000000000 --- a/contrib/remote_hsmd/dump.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef LIGHTNING_CONTRIB_REMOTE_HSMD_DUMP_H -#define LIGHTNING_CONTRIB_REMOTE_HSMD_DUMP_H - -extern "C" { -#include -#include -} -#include - -std::string dump_optional_wallet_index(u32 *optional_wallet_index); -std::string dump_hex(const void *vptr, size_t sz); -std::string dump_basepoints(const struct basepoints *bp); -std::string dump_bitcoin_txid(const struct bitcoin_txid *txid); -std::string dump_bitcoin_signature(const struct bitcoin_signature *sp); -std::string dump_htlc_signatures(const struct bitcoin_signature *sps); -std::string dump_secp256k1_ecdsa_signature(const secp256k1_ecdsa_signature *sp); -std::string dump_secp256k1_ecdsa_recoverable_signature(const secp256k1_ecdsa_recoverable_signature *sp); -std::string dump_secret(const struct secret *sp); -std::string dump_node_id(const struct node_id *pp); -std::string dump_pubkey(const struct pubkey *kp); -std::string dump_ext_pubkey(const struct ext_key *xp); -std::string dump_witnesses(const u8 ***wp); -std::string dump_unilateral_close_info(const struct unilateral_close_info *ip); -std::string dump_utxo(const struct utxo *in); -std::string dump_utxos(const struct utxo **utxos); -std::string dump_bitcoin_tx_output(const struct bitcoin_tx_output *op); -std::string dump_bitcoin_tx_outputs(const struct bitcoin_tx_output **outputs); -std::string dump_wally_tx_witness_stack(const struct wally_tx_witness_stack *sp); -std::string dump_wally_keypath_map(const struct wally_map *mp); -std::string dump_wally_partial_sigs_map(const struct wally_map *mp); -std::string dump_wally_unknowns_map(const struct wally_map *mp); -std::string dump_wally_tx_input(const struct wally_tx_input *in); -std::string dump_wally_tx_inputs(const struct wally_tx_input *inputs, - size_t num_inputs); -std::string dump_wally_tx_output(const struct wally_tx_output *out); -std::string dump_wally_tx_outputs(const struct wally_tx_output *outputs, - size_t num_outputs); -std::string dump_wally_tx(const struct wally_tx *wtx); -std::string dump_wally_psbt(const struct wally_psbt *psbt); -std::string dump_tx(const struct bitcoin_tx *tx); -std::string dump_rhashes(const struct sha256 *rhashes, size_t num_rhashes); -std::string dump_htlc(const struct simple_htlc *htlc); -std::string dump_htlcs(const struct simple_htlc **htlc, size_t num_htlc); - -// needed for formatting txid -void reverse_bytes(u8 *arr, size_t len); - -#endif /* LIGHTNING_CONTRIB_REMOTE_HSMD_DUMP_H */ diff --git a/contrib/remote_hsmd/hsmd.c b/contrib/remote_hsmd/hsmd.c deleted file mode 100644 index c66a5294499c..000000000000 --- a/contrib/remote_hsmd/hsmd.c +++ /dev/null @@ -1,1946 +0,0 @@ -/*~ Welcome to the hsm daemon: keeper of our secrets! - * - * This is a separate daemon which keeps a root secret from which all others - * are generated. It starts with one client: lightningd, which can ask for - * new sockets for other clients. Each client has a simple capability map - * which indicates what it's allowed to ask for. We're entirely driven - * by request, response. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -/*~ _wiregen files are autogenerated by tools/generate-wire.py */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -/*~ Each subdaemon is started with stdin connected to lightningd (for status - * messages), and stderr untouched (for emergency printing). File descriptors - * 3 and beyond are set up on other sockets: for hsmd, fd 3 is the request - * stream from lightningd. */ -#define REQ_FD 3 - -/* This used to be secretstuff, now has no secrets ... */ -static struct { - struct ext_key bip32; /* Only has public part */ -} pubstuff; - -/* Have we initialized the remote signer? */ -static bool initialized = false; - -/* Version codes for BIP32 extended keys in libwally-core. - * It's not suitable to add this struct into client struct, - * so set it static.*/ -static struct bip32_key_version bip32_key_version; - -#if DEVELOPER -/* If they specify --dev-force-privkey it ends up in here. */ -static struct privkey *dev_force_privkey; -/* If they specify --dev-force-bip32-seed it ends up in here. */ -static struct secret *dev_force_bip32_seed; -#endif - -/* FIXME - REMOVE THIS WHEN NO LONGER NEEDED */ -#if 0 -static void print_hex(char const *tag, void const *vptr, size_t sz) -{ - fprintf(stderr, "%s: ", tag); - uint8_t const *ptr = (uint8_t const *) vptr; - for (size_t ii = 0; ii < sz; ++ii) { - fprintf(stderr, "%02x", (int) ptr[ii]); - } - fprintf(stderr, "\n"); -} -#endif - -/*~ We keep track of clients, but there's not much to keep. */ -struct client { - /* The ccan/io async io connection for this client: it closes, we die. */ - struct io_conn *conn; - - /*~ io_read_wire needs a pointer to store incoming messages until - * it has the complete thing; this is it. */ - u8 *msg_in; - - /*~ Useful for logging, but also used to derive the per-channel seed. */ - struct node_id id; - - /*~ This is a unique value handed to us from lightningd, used for - * per-channel seed generation (a single id may have multiple channels - * over time). - * - * It's actually zero for the initial lightningd client connection and - * the ones for gossipd and connectd, which don't have channels - * associated. */ - u64 dbid; - - /* What is this client allowed to ask for? */ - u64 capabilities; - - /* Params to apply to all transactions for this client */ - const struct chainparams *chainparams; -}; - -/*~ We keep a map of nonzero dbid -> clients, mainly for leak detection. - * This is ccan/uintmap, which maps u64 to some (non-NULL) pointer. - * I really dislike these kinds of declaration-via-magic macro things, as - * tags can't find them without special hacks, but the payoff here is that - * the map is typesafe: the compiler won't let you put anything in but a - * struct client pointer. */ -static UINTMAP(struct client *) clients; -/*~ Plus the three zero-dbid clients: master, gossipd and connnectd. */ -static struct client *dbid_zero_clients[3]; -static size_t num_dbid_zero_clients; - -/*~ We need this deep inside bad_req_fmt, and for memleak, so we make it a - * global. */ -static struct daemon_conn *status_conn; - -/* This is used for various assertions and error cases. */ -static bool is_lightningd(const struct client *client) -{ - return client == dbid_zero_clients[0]; -} - -/* Pre-declare this, due to mutual recursion */ -static struct io_plan *handle_client(struct io_conn *conn, struct client *c); - -/*~ ccan/compiler.h defines PRINTF_FMT as the gcc compiler hint so it will - * check that fmt and other trailing arguments really are the correct type. - * - * This is a convenient helper to tell lightningd we've received a bad request - * and closes the client connection. This should never happen, of course, but - * we definitely want to log if it does. - */ -static struct io_plan *bad_req_fmt(struct io_conn *conn, - struct client *c, - const u8 *msg_in, - const char *fmt, ...) - PRINTF_FMT(4,5); - -static struct io_plan *bad_req_fmt(struct io_conn *conn, - struct client *c, - const u8 *msg_in, - const char *fmt, ...) -{ - va_list ap; - char *str; - - va_start(ap, fmt); - str = tal_fmt(tmpctx, fmt, ap); - va_end(ap); - - /*~ If the client was actually lightningd, it's Game Over; we actually - * fail in this case, and it will too. */ - if (is_lightningd(c)) { - status_broken("%s", str); - master_badmsg(fromwire_peektype(msg_in), msg_in); - } - - /*~ Nobody should give us bad requests; it's a sign something is broken */ - status_broken("%s: %s", type_to_string(tmpctx, struct node_id, &c->id), str); - - /*~ Note the use of NULL as the ctx arg to towire_hsmstatus_: only - * use NULL as the allocation when we're about to immediately free it - * or hand it off with take(), as here. That makes it clear we don't - * expect it to linger, and in fact our memleak detection will - * complain if it does (unlike using the deliberately-transient - * tmpctx). */ - daemon_conn_send(status_conn, - take(towire_hsmstatus_client_bad_request(NULL, - &c->id, - str, - msg_in))); - - /*~ The way ccan/io works is that you return the "plan" for what to do - * next (eg. io_read). io_close() is special: it means to close the - * connection. */ - return io_close(conn); -} - -/* Convenience wrapper for when we simply can't parse. */ -static struct io_plan *bad_req(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - return bad_req_fmt(conn, c, msg_in, "could not parse request"); -} - -/*~ This plan simply says: read the next packet into 'c->msg_in' (parent 'c'), - * and then call handle_client with argument 'c' */ -static struct io_plan *client_read_next(struct io_conn *conn, struct client *c) -{ - c->msg_in = tal_free(c->msg_in); - return io_read_wire(conn, c, &c->msg_in, handle_client, c); -} - -/*~ This is the destructor on our client: we may call it manually, but - * generally it's called because the io_conn associated with the client is - * closed by the other end. */ -static void destroy_client(struct client *c) -{ - if (!uintmap_del(&clients, c->dbid)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Failed to remove client dbid %"PRIu64, c->dbid); -} - -static struct client *new_client(const tal_t *ctx, - const struct chainparams *chainparams, - const struct node_id *id, - u64 dbid, - const u64 capabilities, - int fd) -{ - struct client *c = tal(ctx, struct client); - - c->msg_in = NULL; - - /*~ All-zero pubkey is used for the initial master connection */ - if (id) { - c->id = *id; - if (!node_id_valid(id)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Invalid node id %s", - type_to_string(tmpctx, struct node_id, - id)); - } else { - memset(&c->id, 0, sizeof(c->id)); - } - c->dbid = dbid; - - c->capabilities = capabilities; - c->chainparams = chainparams; - - /*~ This is the core of ccan/io: the connection creation calls a - * callback which returns the initial plan to execute: in our case, - * read a message.*/ - c->conn = io_new_conn(ctx, fd, client_read_next, c); - - /*~ tal_steal() moves a pointer to a new parent. At this point, the - * hierarchy is: - * - * ctx -> c - * ctx -> c->conn - * - * We want to the c->conn to own 'c', so that if the io_conn closes, - * the client is freed: - * - * ctx -> c->conn -> c. - */ - tal_steal(c->conn, c); - - /* We put the special zero-db HSM connections into an array, the rest - * go into the map. */ - if (dbid == 0) { - assert(num_dbid_zero_clients < ARRAY_SIZE(dbid_zero_clients)); - dbid_zero_clients[num_dbid_zero_clients++] = c; - } else { - struct client *old_client = uintmap_get(&clients, dbid); - - /* Close conn and free any old client of this dbid. */ - if (old_client) - io_close(old_client->conn); - - if (!uintmap_add(&clients, dbid, c)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Failed inserting dbid %"PRIu64, dbid); - tal_add_destructor(c, destroy_client); - } - - return c; -} - -/* This is the common pattern for the tail of each handler in this file. */ -static struct io_plan *req_reply(struct io_conn *conn, - struct client *c, - const u8 *msg_out TAKES) -{ - /*~ Write this out, then read the next one. This works perfectly for - * a simple request/response system like this. - * - * Internally, the ccan/io subsystem gathers all the file descriptors, - * figures out which want to write and read, asks the OS which ones - * are available, and for those file descriptors, tries to do the - * reads/writes we've asked it. It handles retry in the case where a - * read or write is done partially. - * - * Since the OS does buffering internally (on my system, over 100k - * worth) writes will normally succeed immediately. However, if the - * client is slow or malicious, and doesn't read from the socket as - * fast as we're writing, eventually the socket buffer will fill up; - * we don't care, because ccan/io will wait until there's room to - * write this reply before it will read again. The client just hurts - * themselves, and there's no Denial of Service on us. - * - * If we were to queue outgoing messages ourselves, we *would* have to - * consider such scenarios; this is why our daemons generally avoid - * buffering from untrusted parties. */ - return io_write_wire(conn, msg_out, client_read_next, c); -} - -/* The c-lightning testing framework imbues the hsm_secret with a - * file created before hsmd starts. For now we use the secret from - * the testing framework rather than generating in the remote signer. - * - * Returns true if test seed fetched. If false is returned test seed not - * present, use random instead. - */ -static bool read_test_seed(struct secret *hsm_secret) -{ - struct stat st; - int fd = open("hsm_secret", O_RDONLY); - if (fd < 0) - return false; - if (stat("hsm_secret", &st) != 0) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "stating: %s", strerror(errno)); - - /* If the seed is stored in clear. */ - if (st.st_size > 32) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "hsm_secret not in clear"); - - if (!read_all(fd, hsm_secret, sizeof(*hsm_secret))) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "reading: %s", strerror(errno)); - close(fd); - return true; -} - -// TODO - Add support for bolt12 to remote signer and remove this -// entire routine. This does not actually setup a usable BOLT12 -// context; it always uses an empty hsm_secret. -static void bogus_bolt12_placeholder(struct point32 *bolt12out) -{ - struct secret bad_hsm_secret; - u8 bip32_seed[BIP32_ENTROPY_LEN_256]; - u32 salt = 0; - struct ext_key master_extkey, child_extkey; - secp256k1_keypair bolt12; - - // This needs to be computed on the remote server! - memset(&bad_hsm_secret, 0, sizeof(bad_hsm_secret)); - - /* Fill in the BIP32 tree for bitcoin addresses. */ - /* In libwally-core, the version BIP32_VER_TEST_PRIVATE is for testnet/regtest, - * and BIP32_VER_MAIN_PRIVATE is for mainnet. For litecoin, we also set it like - * bitcoin else.*/ - do { - hkdf_sha256(bip32_seed, sizeof(bip32_seed), - &salt, sizeof(salt), - &bad_hsm_secret, - sizeof(bad_hsm_secret), - "bip32 seed", strlen("bip32 seed")); - salt++; - } while (bip32_key_from_seed(bip32_seed, sizeof(bip32_seed), - bip32_key_version.bip32_privkey_version, - 0, &master_extkey) != WALLY_OK); - - if (bip32_key_from_parent(&master_extkey, - BIP32_INITIAL_HARDENED_CHILD|9735, - BIP32_FLAG_KEY_PRIVATE, - &child_extkey) != WALLY_OK) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Can't derive bolt12 bip32 key"); - - /* libwally says: The private key with prefix byte 0; remove it - * for libsecp256k1. */ - if (secp256k1_keypair_create(secp256k1_ctx, &bolt12, - child_extkey.priv_key+1) != 1) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Can't derive bolt12 keypair"); - - /* We also give it the base key for bolt12 payerids */ - if (secp256k1_keypair_xonly_pub(secp256k1_ctx, &bolt12out->pubkey, NULL, - &bolt12) != 1) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Could derive bolt12 public key."); -} - -// TODO - Add support for onion_reply_secret to remote signer and remove this -// entire routine. This does not actually setup a usable onion_reply_secret -// context; it always uses an empty hsm_secret. -static void bogus_onion_reply_secret_placeholder(struct secret *onion_reply_secret) -{ - // This needs to be computed on the remote server! - memset(&onion_reply_secret, 0, sizeof(onion_reply_secret)); -} - -static void persist_node_id(const struct node_id *node_id) -{ - char *node_id_str = tal_fmt(tmpctx, "%s\n", - type_to_string(tmpctx, struct node_id, node_id)); - int fd = open("NODE_ID", O_WRONLY|O_TRUNC|O_CREAT, 0666); - assert(fd != -1); - write_all(fd, node_id_str, strlen(node_id_str)); - close(fd); -} - -static bool restore_node_id(struct node_id *node_id) -{ - if (access("NODE_ID", F_OK) == -1) { - // This is a cold start, we don't have a node_id yet. - return false; - } - - char *buffer = grab_file(tmpctx, "NODE_ID"); - assert(buffer != NULL); - size_t len = tal_bytelen(buffer) - 2; - assert(buffer[len] == '\n'); - bool ok = node_id_from_hexstr(buffer, len, node_id); - assert(ok); - return true; -} - -/*~ This is the response to lightningd's HSM_INIT request, which is the first - * thing it sends. */ -static struct io_plan *init_hsm(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - struct node_id node_id; - struct point32 bolt12; - struct secret onion_reply_secret; - struct privkey *force_privkey; - struct secret *force_bip32_seed; - struct secrets *force_channel_secrets; - struct sha256 *force_channel_secrets_shaseed; - struct secret *hsm_encryption_key; - struct secret hsm_secret; - struct secret *use_hsm_secret; - bool coldstart; - - /* This must be lightningd. */ - assert(is_lightningd(c)); - - /*~ The fromwire_* routines are autogenerated, based on the message - * definitions in hsm_client_wire.csv. The format of those files is - * an extension of the simple comma-separated format output by the - * BOLT tools/extract-formats.py tool. */ - if (!fromwire_hsmd_init(NULL, msg_in, &bip32_key_version, &chainparams, - &hsm_encryption_key, &force_privkey, - &force_bip32_seed, &force_channel_secrets, - &force_channel_secrets_shaseed)) - return bad_req(conn, c, msg_in); - -#if DEVELOPER - dev_force_privkey = force_privkey; - dev_force_bip32_seed = force_bip32_seed; - dev_force_channel_secrets = force_channel_secrets; - dev_force_channel_secrets_shaseed = force_channel_secrets_shaseed; -#endif - - // We can't force any of these secrets individually, we only - // can set the seed (for testnet integration tests). If we - // see anything being set fail fast. - assert(force_privkey == NULL); - assert(force_bip32_seed == NULL); - assert(force_channel_secrets == NULL); - assert(force_channel_secrets_shaseed == NULL); - - /* The hsm_encryption_key doesn't make any sense with the - * remote signer, fail-fast if it's set. - */ - assert(hsm_encryption_key == NULL); - - /* Once we have read the init message we know which params the master - * will use */ - c->chainparams = chainparams; - - /* Is this a warm start (restart) or a cold start (first time)? */ - if (restore_node_id(&node_id)) { - // This is a warm start. - proxy_set_node_id(&node_id); - } else { - status_unusual("cold start, initializing the remote signer"); - - // This is a cold start, initialize the remote signer. - - /* To support integration tests we honor any seed provided - * in the hsm_secret file (testnet only). Otherwise we - * generate a random seed. - */ - if (read_test_seed(&hsm_secret)) { - // We are running integration tests and the secret has been forced. - use_hsm_secret = &hsm_secret; - } else { - // We are not running integration tests, remote signer generates. - use_hsm_secret = NULL; - } - - coldstart = true; // this can go away in the API. - proxy_stat rv = proxy_init_hsm(&bip32_key_version, chainparams, - coldstart, use_hsm_secret, - &node_id); - if (PROXY_PERMANENT(rv)) { - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - } - else if (!PROXY_SUCCESS(rv)) { - status_unusual("proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - } - - /* Mark this node as already inited. */ - persist_node_id(&node_id); - } - - // Fetch the bip32 ext_pub_key. - proxy_stat rv = proxy_get_ext_pub_key(&pubstuff.bip32); - if (PROXY_PERMANENT(rv)) { - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - } - else if (!PROXY_SUCCESS(rv)) { - status_unusual("proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - } - - // TODO - add support for bolt12 - bogus_bolt12_placeholder(&bolt12); - - // TODO - add support for onion_reply_secret - bogus_onion_reply_secret_placeholder(&onion_reply_secret); - - /* Now we can consider ourselves initialized, and we won't get - * upset if we get a non-init message. */ - initialized = true; - - return req_reply(conn, c, - take(towire_hsmd_init_reply(NULL, &node_id, - &pubstuff.bip32, - &bolt12, &onion_reply_secret))); -} - -/*~ The client has asked us to extract the shared secret from an EC Diffie - * Hellman token. This doesn't leak any information, but requires the private - * key, so the hsmd performs it. It's used to set up an encryption key for the - * connection handshaking (BOLT #8) and for the onion wrapping (BOLT #4). */ -static struct io_plan *handle_ecdh(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - struct pubkey point; - struct secret ss; - - if (!fromwire_hsmd_ecdh_req(msg_in, &point)) - return bad_req(conn, c, msg_in); - - proxy_stat rv = proxy_handle_ecdh(&point, &ss); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - /*~ In the normal case, we return the shared secret, and then read - * the next msg. */ - return req_reply(conn, c, take(towire_hsmd_ecdh_resp(NULL, &ss))); -} - -/*~ The specific routine to sign the channel_announcement message. This is - * defined in BOLT #7, and requires *two* signatures: one from this node's key - * (to prove it's from us), and one from the bitcoin key used to create the - * funding transaction (to prove we own the output). */ -static struct io_plan *handle_cannouncement_sig(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - /*~ Our autogeneration code doesn't define field offsets, so we just - * copy this from the spec itself. - * - * Note that 'check-source' will actually find and check this quote - * against the spec (if available); whitespace is ignored and - * "..." means some content is skipped, but it works remarkably well to - * track spec changes. */ - - /* BOLT #7: - * - * - MUST compute the double-SHA256 hash `h` of the message, beginning - * at offset 256, up to the end of the message. - * - Note: the hash skips the 4 signatures but hashes the rest of the - * message, including any future fields appended to the end. - */ - /* First type bytes are the msg type */ - size_t offset = 2 + 256; - secp256k1_ecdsa_signature node_sig, bitcoin_sig; - u8 *reply; - u8 *ca; - - /*~ You'll find FIXMEs like this scattered through the code. - * Sometimes they suggest simple improvements which someone like - * yourself should go ahead an implement. Sometimes they're deceptive - * quagmires which will cause you nothing but grief. You decide! */ - - /*~ Christian uses TODO(cdecker) or FIXME(cdecker), but I'm sure he won't - * mind if you fix this for him! */ - - /*~ fromwire_ routines which need to do allocation take a tal context - * as their first field; tmpctx is good here since we won't need it - * after this function. */ - if (!fromwire_hsmd_cannouncement_sig_req(tmpctx, msg_in, &ca)) - return bad_req(conn, c, msg_in); - - if (tal_count(ca) < offset) - return bad_req_fmt(conn, c, msg_in, - "bad cannounce length %zu", - tal_count(ca)); - - if (fromwire_peektype(ca) != WIRE_CHANNEL_ANNOUNCEMENT) - return bad_req_fmt(conn, c, msg_in, - "Invalid channel announcement"); - - proxy_stat rv = proxy_handle_cannouncement_sig( - &c->id, c->dbid, ca, - &node_sig, &bitcoin_sig - ); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - reply = towire_hsmd_cannouncement_sig_reply(NULL, &node_sig, - &bitcoin_sig); - return req_reply(conn, c, take(reply)); -} - -/*~ The specific routine to sign the channel_update message. */ -static struct io_plan *handle_channel_update_sig(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - /* BOLT #7: - * - * - MUST set `signature` to the signature of the double-SHA256 of the - * entire remaining packet after `signature`, using its own - * `node_id`. - */ - /* 2 bytes msg type + 64 bytes signature */ - size_t offset = 66; - secp256k1_ecdsa_signature sig; - struct short_channel_id scid; - u32 timestamp, fee_base_msat, fee_proportional_mill; - struct amount_msat htlc_minimum, htlc_maximum; - u8 message_flags, channel_flags; - u16 cltv_expiry_delta; - struct bitcoin_blkid chain_hash; - u8 *cu; - - if (!fromwire_hsmd_cupdate_sig_req(tmpctx, msg_in, &cu)) - return bad_req(conn, c, msg_in); - - if (!fromwire_channel_update_option_channel_htlc_max(cu, &sig, - &chain_hash, &scid, ×tamp, &message_flags, - &channel_flags, &cltv_expiry_delta, - &htlc_minimum, &fee_base_msat, - &fee_proportional_mill, &htlc_maximum)) { - return bad_req_fmt(conn, c, msg_in, "Bad inner channel_update"); - } - if (tal_count(cu) < offset) - return bad_req_fmt(conn, c, msg_in, - "inner channel_update too short"); - - proxy_stat rv = proxy_handle_channel_update_sig(cu, &sig); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - cu = towire_channel_update_option_channel_htlc_max(tmpctx, &sig, &chain_hash, - &scid, timestamp, message_flags, channel_flags, - cltv_expiry_delta, htlc_minimum, - fee_base_msat, fee_proportional_mill, - htlc_maximum); - return req_reply(conn, c, take(towire_hsmd_cupdate_sig_reply(NULL, cu))); -} - -/*~ This gets the basepoints for a channel; it's not private information really - * (we tell the peer this to establish a channel, as it sets up the keys used - * for each transaction). - * - * Note that this is asked by lightningd, so it tells us what channels it wants. - */ -static struct io_plan *handle_get_channel_basepoints(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - struct node_id peer_id; - u64 dbid; - struct basepoints basepoints; - struct pubkey funding_pubkey; - - if (!fromwire_hsmd_get_channel_basepoints(msg_in, &peer_id, &dbid)) - return bad_req(conn, c, msg_in); - - proxy_stat rv = proxy_handle_get_channel_basepoints( - &peer_id, dbid, &basepoints, &funding_pubkey); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - return req_reply(conn, c, - take(towire_hsmd_get_channel_basepoints_reply(NULL, - &basepoints, - &funding_pubkey))); -} - -/*~ This is another lightningd-only interface; signing a commit transaction. - * This is dangerous, since if we sign a revoked commitment tx we'll lose - * funds, thus it's only available to lightningd. - * - * - * Oh look, another FIXME! */ -/* FIXME: Ensure HSM never does this twice for same dbid! */ -static struct io_plan *handle_sign_commitment_tx(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - struct pubkey remote_funding_pubkey; - struct node_id peer_id; - u64 dbid; - struct bitcoin_tx *tx; - u64 commit_num; - struct bitcoin_signature sig; - - if (!fromwire_hsmd_sign_commitment_tx(tmpctx, msg_in, - &peer_id, &dbid, - &tx, - &remote_funding_pubkey, - &commit_num)) - return bad_req(conn, c, msg_in); - - tx->chainparams = c->chainparams; - - /* Basic sanity checks. */ - if (tx->wtx->num_inputs != 1) - return bad_req_fmt(conn, c, msg_in, "tx must have 1 input"); - if (tx->wtx->num_outputs == 0) - return bad_req_fmt(conn, c, msg_in, "tx must have > 0 outputs"); - - // WORKAROUND - sometimes c-lightning calls handle_sign_commitment_tx - // with mutual close transactions. We can tell the difference because - // the locktime field will be set to 0 for a mutual close. - if (tx->wtx->locktime == 0) { - // This is really a mutual close. - fprintf(stderr, - "handle_sign_commitment_tx called with locktime==0; " - "dispatching to proxy_handle_sign_mutual_close_tx instead\n"); - proxy_stat rv = proxy_handle_sign_mutual_close_tx( - tx, &remote_funding_pubkey, &peer_id, dbid, &sig); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - } else { - // This is a unilateral close from our side. - proxy_stat rv = proxy_handle_sign_commitment_tx( - &peer_id, dbid, commit_num, &sig); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - } - - return req_reply(conn, c, - take(towire_hsmd_sign_commitment_tx_reply(NULL, &sig))); -} - -/* Validate the peer's signatures for our commitment and htlc txs. */ -static struct io_plan *handle_validate_commitment_tx(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - struct bitcoin_tx *tx; - struct simple_htlc **htlc; - u64 commit_num; - u32 feerate; - struct bitcoin_signature commit_sig; - struct bitcoin_signature *htlc_sigs; - struct secret *old_secret; - struct pubkey next_per_commitment_point; - - if (!fromwire_hsmd_validate_commitment_tx(tmpctx, msg_in, - &tx, &htlc, - &commit_num, &feerate, - &commit_sig, &htlc_sigs)) - bad_req(conn, c, msg_in); - - proxy_stat rv = proxy_handle_validate_commitment_tx( - tx, - &c->id, c->dbid, - htlc, commit_num, feerate, - &commit_sig, htlc_sigs, - &old_secret, &next_per_commitment_point); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - return req_reply(conn, c, - take(towire_hsmd_validate_commitment_tx_reply( - NULL, old_secret, &next_per_commitment_point))); -} - -/* Validate the peer's signatures for our commitment and htlc txs. */ -static struct io_plan *handle_validate_revocation(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - u64 revoke_num; - struct secret old_secret; - - if (!fromwire_hsmd_validate_revocation(msg_in, - &revoke_num, &old_secret)) - bad_req(conn, c, msg_in); - - proxy_stat rv = proxy_handle_validate_revocation( - &c->id, c->dbid, - revoke_num, &old_secret); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - return req_reply(conn, c, - take(towire_hsmd_validate_revocation_reply(NULL))); -} - -/*~ This is used by channeld to create signatures for the remote peer's - * commitment transaction. It's functionally identical to signing our own, - * but we expect to do this repeatedly as commitment transactions are - * updated. - * - * The HSM almost certainly *should* do more checks before signing! - */ -/* FIXME: make sure it meets some criteria? */ -static struct io_plan *handle_sign_remote_commitment_tx(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - struct pubkey remote_funding_pubkey; - struct bitcoin_tx *tx; - struct bitcoin_signature sig; - struct pubkey remote_per_commit; - bool option_static_remotekey; - u64 commit_num; - struct simple_htlc **htlc; - u32 feerate; - - if (!fromwire_hsmd_sign_remote_commitment_tx(tmpctx, msg_in, - &tx, - &remote_funding_pubkey, - &remote_per_commit, - &option_static_remotekey, - &commit_num, - &htlc, &feerate)) - bad_req(conn, c, msg_in); - tx->chainparams = c->chainparams; - - /* Basic sanity checks. */ - if (tx->wtx->num_inputs != 1) - return bad_req_fmt(conn, c, msg_in, "tx must have 1 input"); - if (tx->wtx->num_outputs == 0) - return bad_req_fmt(conn, c, msg_in, "tx must have > 0 outputs"); - - proxy_stat rv = proxy_handle_sign_remote_commitment_tx( - tx, &remote_funding_pubkey, - &c->id, c->dbid, - &remote_per_commit, - htlc, commit_num, feerate, - &sig); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - STATUS_DEBUG("%s:%d %s: signature: %s", - __FILE__, __LINE__, __FUNCTION__, - type_to_string(tmpctx, struct bitcoin_signature, &sig)); - - return req_reply(conn, c, take(towire_hsmd_sign_tx_reply(NULL, &sig))); -} - -/*~ This is used by channeld to create signatures for the remote peer's - * HTLC transactions. */ -static struct io_plan *handle_sign_remote_htlc_tx(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - struct bitcoin_tx *tx; - struct bitcoin_signature sig; - struct pubkey remote_per_commit_point; - u8 *wscript; - bool option_anchor_outputs; - - if (!fromwire_hsmd_sign_remote_htlc_tx(tmpctx, msg_in, - &tx, &wscript, - &remote_per_commit_point, - &option_anchor_outputs)) - return bad_req(conn, c, msg_in); - tx->chainparams = c->chainparams; - - // The remote signer already knows opt_anchor_outputs from the - // ready channel call. - - proxy_stat rv = proxy_handle_sign_remote_htlc_tx( - tx, - wscript, - &remote_per_commit_point, - &c->id, - c->dbid, - &sig); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - return req_reply(conn, c, take(towire_hsmd_sign_tx_reply(NULL, &sig))); -} - -/*~ When we send a commitment transaction onchain (unilateral close), there's - * a delay before we can spend it. onchaind does an explicit transaction to - * transfer it to the wallet so that doesn't need to remember how to spend - * this complex transaction. */ -static struct io_plan *handle_sign_delayed_payment_to_us(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - u64 commit_num; - struct bitcoin_tx *tx; - u8 *wscript; - - /*~ We don't derive the wscript ourselves, but perhaps we should? */ - if (!fromwire_hsmd_sign_delayed_payment_to_us(tmpctx, msg_in, - &commit_num, - &tx, &wscript)) - return bad_req(conn, c, msg_in); - tx->chainparams = c->chainparams; - - struct bitcoin_signature sig; - proxy_stat rv = proxy_handle_sign_delayed_payment_to_us( - tx, commit_num, wscript, &c->id, c->dbid, &sig); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - return req_reply(conn, c, take(towire_hsmd_sign_tx_reply(NULL, &sig))); -} - -/*~ This is used when a commitment transaction is onchain, and has an HTLC - * output paying to us (because we have the preimage); this signs that - * transaction, which lightningd will broadcast to collect the funds. */ -static struct io_plan *handle_sign_remote_htlc_to_us(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - struct bitcoin_tx *tx; - struct pubkey remote_per_commitment_point; - u8 *wscript; - bool option_anchor_outputs; - - if (!fromwire_hsmd_sign_remote_htlc_to_us(tmpctx, msg_in, - &remote_per_commitment_point, - &tx, &wscript, - &option_anchor_outputs)) - return bad_req(conn, c, msg_in); - - tx->chainparams = c->chainparams; - - // The remote signer already knows opt_anchor_outputs from the - // ready channel call. - - struct bitcoin_signature sig; - proxy_stat rv = proxy_handle_sign_remote_htlc_to_us( - tx, - wscript, - &remote_per_commitment_point, - &c->id, - c->dbid, - &sig); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - return req_reply(conn, c, take(towire_hsmd_sign_tx_reply(NULL, &sig))); -} - -/*~ This is used when the remote peer's commitment transaction is revoked; - * we can use the revocation secret to spend the outputs. For simplicity, - * we do them one at a time, though. */ -static struct io_plan *handle_sign_penalty_to_us(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - struct secret revocation_secret; - struct bitcoin_tx *tx; - u8 *wscript; - - if (!fromwire_hsmd_sign_penalty_to_us(tmpctx, msg_in, - &revocation_secret, - &tx, &wscript)) - return bad_req(conn, c, msg_in); - tx->chainparams = c->chainparams; - - struct bitcoin_signature sig; - proxy_stat rv = proxy_handle_sign_penalty_to_us( - tx, &revocation_secret, wscript, &c->id, c->dbid, &sig); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - return req_reply(conn, c, take(towire_hsmd_sign_tx_reply(NULL, &sig))); -} - -/*~ This is used when a commitment transaction is onchain, and has an HTLC - * output paying to them, which has timed out; this signs that transaction, - * which lightningd will broadcast to collect the funds. */ -static struct io_plan *handle_sign_local_htlc_tx(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - u64 commit_num; - struct bitcoin_tx *tx; - u8 *wscript; - struct bitcoin_signature sig; - bool option_anchor_outputs; - - if (!fromwire_hsmd_sign_local_htlc_tx(tmpctx, msg_in, - &commit_num, &tx, &wscript, - &option_anchor_outputs)) - return bad_req(conn, c, msg_in); - - tx->chainparams = c->chainparams; - - // The remote signer already knows opt_anchor_outputs from the - // ready channel call. - - proxy_stat rv = proxy_handle_sign_local_htlc_tx( - tx, commit_num, wscript, &c->id, c->dbid, &sig); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - return req_reply(conn, c, take(towire_hsmd_sign_tx_reply(NULL, &sig))); -} - -/*~ This get the Nth a per-commitment point, and for N > 2, returns the - * grandparent per-commitment secret. This pattern is because after - * negotiating commitment N-1, we send them the next per-commitment point, - * and reveal the previous per-commitment secret as a promise not to spend - * the previous commitment transaction. */ -static struct io_plan *handle_get_per_commitment_point(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - struct pubkey per_commitment_point; - u64 n; - struct secret *old_secret; - - if (!fromwire_hsmd_get_per_commitment_point(msg_in, &n)) - return bad_req(conn, c, msg_in); - - proxy_stat rv = proxy_handle_get_per_commitment_point( - &c->id, c->dbid, n, - &per_commitment_point, &old_secret); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - /*~ hsm_client_wire.csv marks the secret field here optional, so it only - * gets included if the parameter is non-NULL. We violate 80 columns - * pretty badly here, but it's a recommendation not a religion. */ - return req_reply(conn, c, - take(towire_hsmd_get_per_commitment_point_reply(NULL, - &per_commitment_point, - old_secret))); -} - -/*~ This is used when the remote peer claims to have knowledge of future - * commitment states (option_data_loss_protect in the spec) which means we've - * been restored from backup or something, and may have already revealed - * secrets. We carefully check that this is true, here. */ -static struct io_plan *handle_check_future_secret(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - u64 n; - struct secret suggested; - - if (!fromwire_hsmd_check_future_secret(msg_in, &n, &suggested)) - return bad_req(conn, c, msg_in); - - bool correct; - proxy_stat rv = proxy_handle_check_future_secret( - &c->id, c->dbid, n, &suggested, &correct); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - return req_reply(conn, c, - take(towire_hsmd_check_future_secret_reply(NULL, - correct))); -} - -/* This is used by closingd to sign off on a mutual close tx. */ -static struct io_plan *handle_sign_mutual_close_tx(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - struct bitcoin_tx *tx; - struct pubkey remote_funding_pubkey; - struct bitcoin_signature sig; - - if (!fromwire_hsmd_sign_mutual_close_tx(tmpctx, msg_in, - &tx, - &remote_funding_pubkey)) - return bad_req(conn, c, msg_in); - - tx->chainparams = c->chainparams; - - proxy_stat rv = proxy_handle_sign_mutual_close_tx( - tx, &remote_funding_pubkey, &c->id, c->dbid, &sig); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - return req_reply(conn, c, take(towire_hsmd_sign_tx_reply(NULL, &sig))); -} - -/*~ Since we process requests then service them in strict order, and because - * only lightningd can request a new client fd, we can get away with a global - * here! But because we are being tricky, I set it to an invalid value when - * not in use, and sprinkle assertions around. */ -static int pending_client_fd = -1; - -/*~ This is the callback from below: having sent the reply, we now send the - * fd for the client end of the new socketpair. */ -static struct io_plan *send_pending_client_fd(struct io_conn *conn, - struct client *master) -{ - int fd = pending_client_fd; - /* This must be the master. */ - assert(is_lightningd(master)); - assert(fd != -1); - - /* This sanity check shouldn't be necessary, but it's cheap. */ - pending_client_fd = -1; - - /*~There's arcane UNIX magic to send an open file descriptor over a - * UNIX domain socket. There's no great way to autogenerate this - * though; especially for the receive side, so we always pass these - * manually immediately following the message. - * - * io_send_fd()'s third parameter is whether to close the local one - * after sending; that saves us YA callback. - */ - return io_send_fd(conn, fd, true, client_read_next, master); -} - -/*~ This is used by the master to create a new client connection (which - * becomes the HSM_FD for the subdaemon after forking). */ -static struct io_plan *pass_client_hsmfd(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - int fds[2]; - u64 dbid, capabilities; - struct node_id id; - - /* This must be lightningd itself. */ - assert(is_lightningd(c)); - - if (!fromwire_hsmd_client_hsmfd(msg_in, &id, &dbid, &capabilities)) - return bad_req(conn, c, msg_in); - - /* socketpair is a bi-directional pipe, which is what we want. */ - if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) - status_failed(STATUS_FAIL_INTERNAL_ERROR, "creating fds: %s", - strerror(errno)); - - STATUS_DEBUG("new_client: %"PRIu64, dbid); - new_client(c, c->chainparams, &id, dbid, capabilities, fds[0]); - - // Skip zero dbid (master, gossipd, connectd). - if (dbid != 0) { - proxy_stat rv = proxy_handle_pass_client_hsmfd(&id, dbid, capabilities); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - } - - /*~ We stash this in a global, because we need to get both the fd and - * the client pointer to the callback. The other way would be to - * create a boutique structure and hand that, but we don't need to. */ - pending_client_fd = fds[1]; - return io_write_wire(conn, take(towire_hsmd_client_hsmfd_reply(NULL)), - send_pending_client_fd, c); -} - -/*~ This is used to declare a new channel. */ -static struct io_plan *handle_new_channel(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - struct node_id peer_id; - u64 dbid; - - if (!fromwire_hsmd_new_channel(msg_in, &peer_id, &dbid)) - return bad_req(conn, c, msg_in); - - proxy_stat rv = proxy_handle_new_channel(&peer_id, dbid); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - return req_reply(conn, c, - take(towire_hsmd_new_channel_reply(NULL))); -} - -/*~ This is used to provide all unchanging public channel parameters. */ -static struct io_plan *handle_ready_channel(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - bool is_outbound; - struct amount_sat channel_value; - struct amount_msat push_value; - struct bitcoin_txid funding_txid; - u16 funding_txout; - u16 local_to_self_delay; - u8 *local_shutdown_script; - u32 *local_shutdown_wallet_index; - struct basepoints remote_basepoints; - struct pubkey remote_funding_pubkey; - u16 remote_to_self_delay; - u8 *remote_shutdown_script; - struct channel_type *channel_type; - - if (!fromwire_hsmd_ready_channel(tmpctx, msg_in, &is_outbound, - &channel_value, &push_value, &funding_txid, - &funding_txout, &local_to_self_delay, - &local_shutdown_script, - &local_shutdown_wallet_index, - &remote_basepoints, - &remote_funding_pubkey, - &remote_to_self_delay, - &remote_shutdown_script, - &channel_type)) - return bad_req(conn, c, msg_in); - - proxy_stat rv = proxy_handle_ready_channel( - &c->id, c->dbid, - is_outbound, - &channel_value, - &push_value, - &funding_txid, - funding_txout, - local_to_self_delay, // locally imposed on counterparty to_self outputs - local_shutdown_script, - local_shutdown_wallet_index, - &remote_basepoints, - &remote_funding_pubkey, - remote_to_self_delay, // counterparty imposed on our to_self outputs - remote_shutdown_script, - channel_type); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - return req_reply(conn, c, - take(towire_hsmd_ready_channel_reply(NULL))); -} - -/*~ lightningd asks us to sign a withdrawal; same as above but in theory - * we can do more to check the previous case is valid. */ -static struct io_plan *handle_sign_withdrawal_tx(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - struct utxo **utxos; - struct wally_psbt *psbt; - - if (!fromwire_hsmd_sign_withdrawal(tmpctx, msg_in, - &utxos, &psbt)) - return bad_req(conn, c, msg_in); - - struct bitcoin_tx_output **outputs; - outputs = tal_arr(tmpctx, struct bitcoin_tx_output *, psbt->num_outputs); - for (size_t ii = 0; ii < psbt->num_outputs; ++ii) { - outputs[ii] = tal(outputs, struct bitcoin_tx_output); - outputs[ii]->amount.satoshis = psbt->tx->outputs[ii].satoshi; /* Raw: from wally_tx_output */ - outputs[ii]->script = - tal_dup_arr(outputs[ii], u8, - psbt->tx->outputs[ii].script, - psbt->tx->outputs[ii].script_len, 0); - } - - u8 *** wits; - proxy_stat rv = proxy_handle_sign_withdrawal_tx( - outputs, utxos, psbt, &wits); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - /* We must have one witness element for each input. */ - assert(tal_count(wits) == psbt->num_inputs); - - /* Witnesses and inputs are in the same order. */ - for (size_t kk = 0; kk < tal_count(wits); ++kk) { - struct wally_psbt_input *input = &psbt->inputs[kk]; - u8 *sig = wits[kk][0]; - u8 *pubkey = wits[kk][1]; - int ret; - - /* If this is a PSBT, we should skip any inputs that - * have an empty signature. */ - if (tal_count(sig) == 0) - continue; - - struct pubkey spubkey; - pubkey_from_der(pubkey, EC_PUBLIC_KEY_LEN, &spubkey); - psbt_input_add_pubkey(psbt, kk, &spubkey); - - tal_wally_start(); - ret = wally_psbt_input_add_signature( - input, - pubkey, EC_PUBLIC_KEY_LEN, - sig, tal_count(sig)); - assert(ret == WALLY_OK); - tal_wally_end(psbt); - } - - return req_reply(conn, c, - take(towire_hsmd_sign_withdrawal_reply(NULL, psbt))); -} - -static struct io_plan *handle_get_output_scriptpubkey(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - struct unilateral_close_info info; - - info.commitment_point = NULL; - if (!fromwire_hsmd_get_output_scriptpubkey(tmpctx, msg_in, - &info.channel_id, - &info.peer_id, - &info.commitment_point)) - return bad_req(conn, c, msg_in); - - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "handle_get_output_scriptpubkey unimplemented"); - - return bad_req_fmt(conn, c, msg_in, - "handle_get_output_scriptpubkey unimplemented"); -} - -/*~ Lightning invoices, defined by BOLT 11, are signed. This has been - * surprisingly controversial; it means a node needs to be online to create - * invoices. However, it seems clear to me that in a world without - * intermedaries you need proof that you have received an offer (the - * signature), as well as proof that you've paid it (the preimage). */ -static struct io_plan *handle_sign_invoice(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - /*~ We make up a 'u5' type to represent BOLT11's 5-bits-per-byte - * format: it's only for human consumption, as typedefs are almost - * entirely transparent to the C compiler. */ - u5 *u5bytes; - u8 *hrpu8; - secp256k1_ecdsa_recoverable_signature rsig; - - if (!fromwire_hsmd_sign_invoice(tmpctx, msg_in, &u5bytes, &hrpu8)) - return bad_req(conn, c, msg_in); - - proxy_stat rv = proxy_handle_sign_invoice(u5bytes, hrpu8, &rsig); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - return req_reply(conn, c, - take(towire_hsmd_sign_invoice_reply(NULL, &rsig))); -} - -/*~ It's optional for nodes to send node_announcement, but it lets us set our - * favourite color and cool alias! Plus other minor details like how to - * connect to us. */ -static struct io_plan *handle_sign_node_announcement(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - /* BOLT #7: - * - * The origin node: - *... - * - MUST set `signature` to the signature of the double-SHA256 of the - * entire remaining packet after `signature` (using the key given by - * `node_id`). - */ - /* 2 bytes msg type + 64 bytes signature */ - size_t offset = 66; - secp256k1_ecdsa_signature sig; - u8 *reply; - u8 *ann; - - if (!fromwire_hsmd_node_announcement_sig_req(tmpctx, msg_in, &ann)) - return bad_req(conn, c, msg_in); - - if (tal_count(ann) < offset) - return bad_req_fmt(conn, c, msg_in, - "Node announcement too short"); - - if (fromwire_peektype(ann) != WIRE_NODE_ANNOUNCEMENT) - return bad_req_fmt(conn, c, msg_in, - "Invalid announcement"); - - proxy_stat rv = proxy_handle_sign_node_announcement(ann, &sig); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - reply = towire_hsmd_node_announcement_sig_reply(NULL, &sig); - return req_reply(conn, c, take(reply)); -} - -/*~ lightningd asks us to sign a message. I tweeted the spec - * in https://twitter.com/rusty_twit/status/1182102005914800128: - * - * @roasbeef & @bitconner point out that #lnd algo is: - * zbase32(SigRec(SHA256(SHA256("Lightning Signed Message:" + msg)))). - * zbase32 from https://philzimmermann.com/docs/human-oriented-base-32-encoding.txt - * and SigRec has first byte 31 + recovery id, followed by 64 byte sig. #specinatweet - */ -static struct io_plan *handle_sign_message(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - u8 *msg; - secp256k1_ecdsa_recoverable_signature rsig; - - if (!fromwire_hsmd_sign_message(tmpctx, msg_in, &msg)) - return bad_req(conn, c, msg_in); - - proxy_stat rv = proxy_handle_sign_message(msg, &rsig); - if (PROXY_PERMANENT(rv)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "proxy_%s failed: %s", __FUNCTION__, - proxy_last_message()); - else if (!PROXY_SUCCESS(rv)) - return bad_req_fmt(conn, c, msg_in, - "proxy_%s error: %s", __FUNCTION__, - proxy_last_message()); - - return req_reply(conn, c, - take(towire_hsmd_sign_message_reply(NULL, &rsig))); -} - -/*~ lightningd asks us to sign a liquidity ad offer */ -static struct io_plan *handle_sign_option_will_fund_offer(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - struct pubkey funding_pubkey; - u32 lease_expiry, channel_fee_base_max_msat; - u16 channel_fee_max_ppt; - - if (!fromwire_hsmd_sign_option_will_fund_offer(msg_in, - &funding_pubkey, - &lease_expiry, - &channel_fee_base_max_msat, - &channel_fee_max_ppt)) - return bad_req(conn, c, msg_in); - - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "handle_sign_option_will_fund_offer unimplemented"); - return bad_req_fmt(conn, c, msg_in, - "handle_sign_option_will_fund_offer unimplemented"); -} - -/*~ lightningd asks us to sign a bolt12 (e.g. offer). */ -static struct io_plan *handle_sign_bolt12(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - char *messagename, *fieldname; - struct sha256 merkle; - u8 *publictweak; - - if (!fromwire_hsmd_sign_bolt12(tmpctx, msg_in, - &messagename, &fieldname, &merkle, - &publictweak)) - return bad_req(conn, c, msg_in); - - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "handle_sign_bolt12 unimplemented"); - return bad_req_fmt(conn, c, msg_in, - "handle_sign_bolt12 unimplemented"); -} - -#if DEVELOPER -static struct io_plan *handle_memleak(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - struct htable *memtable; - bool found_leak; - u8 *reply; - - memtable = memleak_find_allocations(tmpctx, msg_in, msg_in); - - /* Now delete clients and anything they point to. */ - memleak_remove_region(memtable, - dbid_zero_clients, sizeof(dbid_zero_clients)); - memleak_remove_uintmap(memtable, &clients); - memleak_remove_region(memtable, - status_conn, tal_bytelen(status_conn)); - - memleak_remove_pointer(memtable, dev_force_privkey); - memleak_remove_pointer(memtable, dev_force_bip32_seed); - - found_leak = dump_memleak(memtable, memleak_status_broken); - reply = towire_hsmd_dev_memleak_reply(NULL, found_leak); - return req_reply(conn, c, take(reply)); -} -#endif /* DEVELOPER */ - -/*~ This routine checks that a client is allowed to call the handler. */ -static bool check_client_capabilities(struct client *client, - enum hsmd_wire t) -{ - /*~ Here's a useful trick: enums in C are not real types, they're - * semantic sugar sprinkled over an int, bascally (in fact, older - * versions of gcc used to convert the values ints in the parser!). - * - * But GCC will do one thing for us: if we have a switch statement - * with a controlling expression which is an enum, it will warn us - * if a declared enum value is *not* handled in the switch, eg: - * enumeration value ‘FOOBAR’ not handled in switch [-Werror=switch] - * - * This only works if there's no 'default' label, which is sometimes - * hard, as we *can* have non-enum values in our enum. But the tradeoff - * is worth it so the compiler tells us everywhere we have to fix when - * we add a new enum identifier! - */ - switch (t) { - case WIRE_HSMD_ECDH_REQ: - return (client->capabilities & HSM_CAP_ECDH) != 0; - - case WIRE_HSMD_CANNOUNCEMENT_SIG_REQ: - case WIRE_HSMD_CUPDATE_SIG_REQ: - case WIRE_HSMD_NODE_ANNOUNCEMENT_SIG_REQ: - return (client->capabilities & HSM_CAP_SIGN_GOSSIP) != 0; - - case WIRE_HSMD_SIGN_DELAYED_PAYMENT_TO_US: - case WIRE_HSMD_SIGN_REMOTE_HTLC_TO_US: - case WIRE_HSMD_SIGN_PENALTY_TO_US: - case WIRE_HSMD_SIGN_LOCAL_HTLC_TX: - return (client->capabilities & HSM_CAP_SIGN_ONCHAIN_TX) != 0; - - case WIRE_HSMD_GET_PER_COMMITMENT_POINT: - case WIRE_HSMD_CHECK_FUTURE_SECRET: - case WIRE_HSMD_READY_CHANNEL: - return (client->capabilities & HSM_CAP_COMMITMENT_POINT) != 0; - - case WIRE_HSMD_SIGN_REMOTE_COMMITMENT_TX: - case WIRE_HSMD_SIGN_REMOTE_HTLC_TX: - case WIRE_HSMD_VALIDATE_COMMITMENT_TX: - case WIRE_HSMD_VALIDATE_REVOCATION: - return (client->capabilities & HSM_CAP_SIGN_REMOTE_TX) != 0; - - case WIRE_HSMD_SIGN_MUTUAL_CLOSE_TX: - return (client->capabilities & HSM_CAP_SIGN_CLOSING_TX) != 0; - - case WIRE_HSMD_SIGN_OPTION_WILL_FUND_OFFER: - return (client->capabilities & HSM_CAP_SIGN_WILL_FUND_OFFER) != 0; - - case WIRE_HSMD_INIT: - case WIRE_HSMD_NEW_CHANNEL: - case WIRE_HSMD_CLIENT_HSMFD: - case WIRE_HSMD_SIGN_WITHDRAWAL: - case WIRE_HSMD_SIGN_INVOICE: - case WIRE_HSMD_SIGN_COMMITMENT_TX: - case WIRE_HSMD_GET_CHANNEL_BASEPOINTS: - case WIRE_HSMD_DEV_MEMLEAK: - case WIRE_HSMD_SIGN_MESSAGE: - case WIRE_HSMD_GET_OUTPUT_SCRIPTPUBKEY: - case WIRE_HSMD_SIGN_BOLT12: - return (client->capabilities & HSM_CAP_MASTER) != 0; - - /*~ These are messages sent by the HSM so we should never receive them. */ - /* FIXME: Since we autogenerate these, we should really generate separate - * enums for replies to avoid this kind of clutter! */ - case WIRE_HSMD_ECDH_RESP: - case WIRE_HSMD_CANNOUNCEMENT_SIG_REPLY: - case WIRE_HSMD_CUPDATE_SIG_REPLY: - case WIRE_HSMD_CLIENT_HSMFD_REPLY: - case WIRE_HSMD_NEW_CHANNEL_REPLY: - case WIRE_HSMD_READY_CHANNEL_REPLY: - case WIRE_HSMD_NODE_ANNOUNCEMENT_SIG_REPLY: - case WIRE_HSMD_SIGN_WITHDRAWAL_REPLY: - case WIRE_HSMD_SIGN_INVOICE_REPLY: - case WIRE_HSMD_INIT_REPLY: - case WIRE_HSMSTATUS_CLIENT_BAD_REQUEST: - case WIRE_HSMD_SIGN_COMMITMENT_TX_REPLY: - case WIRE_HSMD_VALIDATE_COMMITMENT_TX_REPLY: - case WIRE_HSMD_VALIDATE_REVOCATION_REPLY: - case WIRE_HSMD_SIGN_TX_REPLY: - case WIRE_HSMD_SIGN_OPTION_WILL_FUND_OFFER_REPLY: - case WIRE_HSMD_GET_PER_COMMITMENT_POINT_REPLY: - case WIRE_HSMD_CHECK_FUTURE_SECRET_REPLY: - case WIRE_HSMD_GET_CHANNEL_BASEPOINTS_REPLY: - case WIRE_HSMD_DEV_MEMLEAK_REPLY: - case WIRE_HSMD_SIGN_MESSAGE_REPLY: - case WIRE_HSMD_GET_OUTPUT_SCRIPTPUBKEY_REPLY: - case WIRE_HSMD_SIGN_BOLT12_REPLY: - break; - } - return false; -} - -/*~ This is the core of the HSM daemon: handling requests. */ -static struct io_plan *handle_client(struct io_conn *conn, struct client *c) -{ - enum hsmd_wire t = fromwire_peektype(c->msg_in); - - if (!is_lightningd(c)) - status_peer_debug(&c->id, "Got %s", hsmd_wire_name(t)); - - STATUS_DEBUG("Client: Received message %d from client", t); - - /* Before we do anything else, is this client allowed to do - * what he asks for? */ - if (!check_client_capabilities(c, t)) - return bad_req_fmt(conn, c, c->msg_in, - "does not have capability to run %d", t); - - /* If we aren't initialized yet we better get an init message - * first. Otherwise we don't load the secret and every - * signature we produce is just going to be junk. */ - if (!initialized && t != WIRE_HSMD_INIT) - status_failed(STATUS_FAIL_MASTER_IO, - "hsmd was not initialized correctly, expected " - "message type %d, got %d", - WIRE_HSMD_INIT, t); - - /* Now actually go and do what the client asked for */ - switch (t) { - case WIRE_HSMD_INIT: - return init_hsm(conn, c, c->msg_in); - - case WIRE_HSMD_CLIENT_HSMFD: - return pass_client_hsmfd(conn, c, c->msg_in); - - case WIRE_HSMD_NEW_CHANNEL: - return handle_new_channel(conn, c, c->msg_in); - - case WIRE_HSMD_READY_CHANNEL: - return handle_ready_channel(conn, c, c->msg_in); - - case WIRE_HSMD_GET_CHANNEL_BASEPOINTS: - return handle_get_channel_basepoints(conn, c, c->msg_in); - - case WIRE_HSMD_GET_OUTPUT_SCRIPTPUBKEY: - return handle_get_output_scriptpubkey(conn, c, c->msg_in); - - case WIRE_HSMD_ECDH_REQ: - return handle_ecdh(conn, c, c->msg_in); - - case WIRE_HSMD_CANNOUNCEMENT_SIG_REQ: - return handle_cannouncement_sig(conn, c, c->msg_in); - - case WIRE_HSMD_CUPDATE_SIG_REQ: - return handle_channel_update_sig(conn, c, c->msg_in); - - case WIRE_HSMD_NODE_ANNOUNCEMENT_SIG_REQ: - return handle_sign_node_announcement(conn, c, c->msg_in); - - case WIRE_HSMD_SIGN_INVOICE: - return handle_sign_invoice(conn, c, c->msg_in); - - case WIRE_HSMD_SIGN_WITHDRAWAL: - return handle_sign_withdrawal_tx(conn, c, c->msg_in); - - case WIRE_HSMD_SIGN_COMMITMENT_TX: - return handle_sign_commitment_tx(conn, c, c->msg_in); - - case WIRE_HSMD_VALIDATE_COMMITMENT_TX: - return handle_validate_commitment_tx(conn, c, c->msg_in); - - case WIRE_HSMD_VALIDATE_REVOCATION: - return handle_validate_revocation(conn, c, c->msg_in); - - case WIRE_HSMD_SIGN_DELAYED_PAYMENT_TO_US: - return handle_sign_delayed_payment_to_us(conn, c, c->msg_in); - - case WIRE_HSMD_SIGN_REMOTE_HTLC_TO_US: - return handle_sign_remote_htlc_to_us(conn, c, c->msg_in); - - case WIRE_HSMD_SIGN_PENALTY_TO_US: - return handle_sign_penalty_to_us(conn, c, c->msg_in); - - case WIRE_HSMD_SIGN_LOCAL_HTLC_TX: - return handle_sign_local_htlc_tx(conn, c, c->msg_in); - - case WIRE_HSMD_GET_PER_COMMITMENT_POINT: - return handle_get_per_commitment_point(conn, c, c->msg_in); - - case WIRE_HSMD_CHECK_FUTURE_SECRET: - return handle_check_future_secret(conn, c, c->msg_in); - - case WIRE_HSMD_SIGN_REMOTE_COMMITMENT_TX: - return handle_sign_remote_commitment_tx(conn, c, c->msg_in); - - case WIRE_HSMD_SIGN_REMOTE_HTLC_TX: - return handle_sign_remote_htlc_tx(conn, c, c->msg_in); - - case WIRE_HSMD_SIGN_MUTUAL_CLOSE_TX: - return handle_sign_mutual_close_tx(conn, c, c->msg_in); - - case WIRE_HSMD_SIGN_MESSAGE: - return handle_sign_message(conn, c, c->msg_in); - - case WIRE_HSMD_SIGN_OPTION_WILL_FUND_OFFER: - return handle_sign_option_will_fund_offer(conn, c, c->msg_in); - - case WIRE_HSMD_SIGN_BOLT12: - return handle_sign_bolt12(conn, c, c->msg_in); -#if DEVELOPER - case WIRE_HSMD_DEV_MEMLEAK: - return handle_memleak(conn, c, c->msg_in); -#else - case WIRE_HSMD_DEV_MEMLEAK: -#endif /* DEVELOPER */ - case WIRE_HSMD_ECDH_RESP: - case WIRE_HSMD_CANNOUNCEMENT_SIG_REPLY: - case WIRE_HSMD_CUPDATE_SIG_REPLY: - case WIRE_HSMD_CLIENT_HSMFD_REPLY: - case WIRE_HSMD_NEW_CHANNEL_REPLY: - case WIRE_HSMD_READY_CHANNEL_REPLY: - case WIRE_HSMD_NODE_ANNOUNCEMENT_SIG_REPLY: - case WIRE_HSMD_SIGN_WITHDRAWAL_REPLY: - case WIRE_HSMD_SIGN_INVOICE_REPLY: - case WIRE_HSMD_INIT_REPLY: - case WIRE_HSMSTATUS_CLIENT_BAD_REQUEST: - case WIRE_HSMD_SIGN_COMMITMENT_TX_REPLY: - case WIRE_HSMD_VALIDATE_COMMITMENT_TX_REPLY: - case WIRE_HSMD_VALIDATE_REVOCATION_REPLY: - case WIRE_HSMD_SIGN_TX_REPLY: - case WIRE_HSMD_SIGN_OPTION_WILL_FUND_OFFER_REPLY: - case WIRE_HSMD_GET_PER_COMMITMENT_POINT_REPLY: - case WIRE_HSMD_CHECK_FUTURE_SECRET_REPLY: - case WIRE_HSMD_GET_CHANNEL_BASEPOINTS_REPLY: - case WIRE_HSMD_DEV_MEMLEAK_REPLY: - case WIRE_HSMD_SIGN_MESSAGE_REPLY: - case WIRE_HSMD_GET_OUTPUT_SCRIPTPUBKEY_REPLY: - case WIRE_HSMD_SIGN_BOLT12_REPLY: - break; - } - - return bad_req_fmt(conn, c, c->msg_in, "Unknown request"); -} - -static void master_gone(struct io_conn *unused UNUSED, struct client *c UNUSED) -{ - daemon_shutdown(); - /* Can't tell master, it's gone. */ - exit(2); -} - -int main(int argc, char *argv[]) -{ - struct client *master; - - setup_locale(); - - /* This sets up tmpctx, various DEVELOPER options, backtraces, etc. */ - subdaemon_setup(argc, argv); - - /* A trivial daemon_conn just for writing. */ - status_conn = daemon_conn_new(NULL, STDIN_FILENO, NULL, NULL, NULL); - status_setup_async(status_conn); - uintmap_init(&clients); - - master = new_client(NULL, NULL, NULL, 0, - HSM_CAP_MASTER | HSM_CAP_SIGN_GOSSIP | HSM_CAP_ECDH, - REQ_FD); - - /* First client == lightningd. */ - assert(is_lightningd(master)); - - /* When conn closes, everything is freed. */ - io_set_finish(master->conn, master_gone, master); - - /* Setup the remote proxy */ - proxy_setup(); - - /*~ The two NULL args are a list of timers, and the timer which expired: - * we don't have any timers. */ - io_loop(NULL, NULL); - - /*~ This should never be reached: io_loop only exits on io_break which - * we don't call, a timer expiry which we don't have, or all connections - * being closed, and closing the master calls master_gone. */ - abort(); -} - -/*~ Congratulations on making it through the first of the seven dwarves! - * (And Christian wondered why I'm so fond of having separate daemons!). - * - * We continue our story in the next-more-complex daemon: connectd/connectd.c - */ diff --git a/contrib/remote_hsmd/proxy.cc b/contrib/remote_hsmd/proxy.cc deleted file mode 100644 index f9638755f58b..000000000000 --- a/contrib/remote_hsmd/proxy.cc +++ /dev/null @@ -1,1684 +0,0 @@ -/* This needs to be first */ -#define __STDC_FORMAT_MACROS - -#include "contrib/remote_hsmd/dump.hpp" -#include "contrib/remote_hsmd/proxy.hpp" -#include "contrib/remote_hsmd/remotesigner.grpc.pb.h" -#include "contrib/remote_hsmd/remotesigner.pb.h" -extern "C" { -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -} -#include -#include -#include -extern "C" { -#include -} -#include -#include /* These two only needed for sleep() and getpid() */ -#include -extern "C" { -#include -#include -} - - -using std::cerr; -using std::endl; -using std::ostringstream; -using std::string; -using std::unique_ptr; - -using grpc::Channel; -using grpc::ClientContext; -using grpc::Status; -using grpc::StatusCode; - -using ::google::protobuf::RepeatedPtrField; - -using namespace remotesigner; - -namespace { -unique_ptr stub; -string last_message; -struct node_id self_id; - -proxy_stat map_status(Status const & status) -{ - StatusCode code = status.error_code(); - - // FIXME - this is bogus, but the pytest framework loses our - // status_unusual messages. - if (code != StatusCode::OK) { - cerr << "PID: " << getpid() << ' ' - << "PROXY-HSMD grpc::StatusCode " << int(code) - << ": " << status.error_message() - << endl; - } - switch (code) { - case StatusCode::OK: return PROXY_OK; - case StatusCode::CANCELLED: return PROXY_CANCELLED; - case StatusCode::DEADLINE_EXCEEDED: return PROXY_TIMEOUT; - case StatusCode::UNAVAILABLE: return PROXY_UNAVAILABLE; - case StatusCode::INVALID_ARGUMENT: return PROXY_INVALID_ARGUMENT; - case StatusCode::INTERNAL: return PROXY_INTERNAL_ERROR; - default: - cerr << "UNHANDLED grpc::StatusCode " << int(code) - << ": " << status.error_message() - << endl; - abort(); - } -} - -/* BIP144: - * If the witness is empty, the old serialization format should be used. */ -bool uses_witness(const struct wally_tx *wtx) -{ - size_t i; - for (i = 0; i < wtx->num_inputs; i++) { - if (wtx->inputs[i].witness) - return true; - } - return false; -} - - -string serialized_wtx(struct wally_tx const *wtx, bool bip144) -{ - int res; - size_t len, written; - u8 *serialized;; - u8 flag = 0; - - if (bip144 && uses_witness(wtx)) - flag |= WALLY_TX_FLAG_USE_WITNESS; - - res = wally_tx_get_length(wtx, flag, &len); - assert(res == WALLY_OK); - - string retval(len, '\0'); - res = wally_tx_to_bytes(wtx, flag, (unsigned char *)&retval[0], - retval.size(), &written); - assert(res == WALLY_OK); - assert(len == written); - return retval; -} - -void marshal_channel_nonce(struct node_id const *peer_id, u64 dbid, - ChannelNonce *o_np) -{ - o_np->set_data(string((char const *)peer_id->k, sizeof(peer_id->k)) + - string((char const *)&dbid, sizeof(dbid))); -} - -void marshal_secret(struct secret const *ss, Secret *o_sp) -{ - o_sp->set_data(ss->data, sizeof(ss->data)); -} - -void marshal_bip32seed(struct secret const *ss, BIP32Seed *o_sp) -{ - o_sp->set_data(ss->data, sizeof(ss->data)); -} - -void marshal_node_id(struct node_id const *np, NodeId *o_np) -{ - o_np->set_data(np->k, sizeof(np->k)); -} - -void marshal_pubkey(struct pubkey const *pp, PubKey *o_pp) -{ - u8 pubkey_der[PUBKEY_CMPR_LEN]; - pubkey_to_der(pubkey_der, pp); - - o_pp->set_data(pubkey_der, sizeof(pubkey_der)); -} - -void marshal_utxo(struct utxo const *up, InputDescriptor *idesc) -{ - idesc->mutable_key_loc()->add_key_path(up->keyindex); - idesc->set_value_sat(up->amount.satoshis); - idesc->set_spend_type(up->is_p2sh - ? SpendType::P2SH_P2WPKH - : SpendType::P2WPKH); - if (up->close_info) { - UnilateralCloseInfo *cinfo = - idesc->mutable_key_loc()->mutable_close_info(); - marshal_channel_nonce(&up->close_info->peer_id, - up->close_info->channel_id, - cinfo->mutable_channel_nonce()); - if (up->close_info->commitment_point) - marshal_pubkey(up->close_info->commitment_point, - cinfo->mutable_commitment_point()); - } -} - -void marshal_outpoint(struct bitcoin_txid const *txid, u16 txout, Outpoint *o_op) -{ - o_op->set_txid(txid->shad.sha.u.u8, sizeof(txid->shad.sha.u.u8)); - o_op->set_index(txout); -} - -void marshal_script(u8 const *script, string *o_script) -{ - if (script) - o_script->assign((char const *)script, tal_count(script)); -} - -void marshal_bitcoin_signature(struct bitcoin_signature const *sp, BitcoinSignature *o_sig) -{ - u8 der[73]; - size_t len = signature_to_der(der, sp); - o_sig->set_data(der, len); -} - -void marshal_basepoints(struct basepoints const *bps, - struct pubkey *funding_pubkey, - Basepoints * o_bps) -{ - marshal_pubkey(&bps->revocation, o_bps->mutable_revocation()); - marshal_pubkey(&bps->payment, o_bps->mutable_payment()); - marshal_pubkey(&bps->htlc, o_bps->mutable_htlc()); - marshal_pubkey(&bps->delayed_payment, o_bps->mutable_delayed_payment()); - marshal_pubkey(funding_pubkey, o_bps->mutable_funding_pubkey()); -} - -void marshal_single_input_tx(struct bitcoin_tx const *tx, - u8 const *redeem_script, - Transaction *o_tp) -{ - assert(tx->psbt->num_outputs == tx->wtx->num_outputs); - - o_tp->set_raw_tx_bytes(serialized_wtx(tx->wtx, true)); - - assert(tx->wtx->num_inputs == 1); - assert(tx->psbt->num_inputs == 1); - InputDescriptor *idesc = o_tp->add_input_descs(); - idesc->set_value_sat(psbt_input_get_amount(tx->psbt, 0).satoshis); - if (redeem_script) - idesc->set_redeem_script((const char *) redeem_script, - tal_count(redeem_script)); - - for (size_t ii = 0; ii < tx->wtx->num_outputs; ii++) { - OutputDescriptor *odesc = o_tp->add_output_descs(); - - // Add witness script - if (tx->psbt->outputs[ii].witness_script_len) - odesc->set_witscript( - (const char *) - tx->psbt->outputs[ii].witness_script, - tx->psbt->outputs[ii].witness_script_len); - - // Add keypath - struct wally_map *mp = &tx->psbt->outputs[ii].keypaths; - if (mp->num_items == 1) { - const struct wally_map_item *ip = &mp->items[0]; - size_t npath = - (ip->value_len - BIP32_KEY_FINGERPRINT_LEN) / sizeof(uint32_t); - uint32_t *path = (uint32_t *) (ip->value + BIP32_KEY_FINGERPRINT_LEN); - for (size_t jj = 0; jj < npath; ++jj) { - odesc->mutable_key_loc()->add_key_path(le32_to_cpu(path[jj])); - } - } - - } -} - -void marshal_rhashes(const struct sha256 *rhashes, - RepeatedPtrField *payment_hashes) -{ - for (size_t ii = 0; ii < tal_count(rhashes); ++ii) { - payment_hashes->Add(string((const char *) &rhashes[ii], - sizeof(struct sha256))); - } -} - -void marshal_htlc(const struct simple_htlc *htlc, HTLCInfo *o_htlc) -{ - o_htlc->set_value_sat(htlc->amount.millisatoshis / 1000); - o_htlc->set_payment_hash(&htlc->payment_hash, sizeof(htlc->payment_hash)); - o_htlc->set_cltv_expiry(htlc->cltv_expiry); -} - -void unmarshal_secret(Secret const &ss, struct secret *o_sp) -{ - assert(ss.data().size() == sizeof(o_sp->data)); - memcpy(o_sp->data, ss.data().data(), sizeof(o_sp->data)); - -} -void unmarshal_node_id(NodeId const &nn, struct node_id *o_np) -{ - assert(nn.data().size() == sizeof(o_np->k)); - memcpy(o_np->k, nn.data().data(), sizeof(o_np->k)); -} - -void unmarshal_pubkey(PubKey const &pk, struct pubkey *o_pp) -{ - bool ok = pubkey_from_der((u8 const *)pk.data().data(), - pk.data().size(), - o_pp); - assert(ok); -} - -void unmarshal_ext_pubkey(ExtPubKey const &xpk, struct ext_key *o_xp) -{ - int rv = bip32_key_from_base58(xpk.encoded().data(), o_xp); - assert(rv == WALLY_OK); -} - -void unmarshal_bitcoin_signature(BitcoinSignature const &bs, - struct bitcoin_signature *o_sig) -{ - bool ok = signature_from_der( - (const u8*)bs.data().data(), - bs.data().size(), - o_sig); - assert(ok); -} - -void unmarshal_ecdsa_signature(ECDSASignature const &es, - secp256k1_ecdsa_signature *o_sig) -{ - int ok = secp256k1_ecdsa_signature_parse_der( - secp256k1_ctx, - o_sig, - (const u8*)es.data().data(), - es.data().size()); - assert(ok); -} - -void unmarshal_ecdsa_recoverable_signature(ECDSARecoverableSignature const &es, - secp256k1_ecdsa_recoverable_signature *o_sig) -{ - assert(es.data().size() == 65); - int recid = es.data().data()[64]; - int ok = secp256k1_ecdsa_recoverable_signature_parse_compact( - secp256k1_ctx, - o_sig, - (const u8*)es.data().data(), - recid); - assert(ok); -} - -void unmarshal_witnesses(RepeatedPtrField const &wits, u8 ****o_wits) -{ - u8 ***owits = NULL; - int nwits = wits.size(); - if (nwits > 0) { - owits = tal_arrz(tmpctx, u8**, nwits); - for (size_t ii = 0; ii < nwits; ++ii) { - owits[ii] = tal_arrz(owits, u8*, 2); - Witness const &wit = wits.Get(ii); - const string &sig = wit.signature().data(); - const string &pubkey = wit.pubkey().data(); - owits[ii][0] = tal_arr(owits[ii], u8, sig.size()); - memcpy(owits[ii][0], sig.data(), sig.size()); - owits[ii][1] = tal_arr(owits[ii], u8, pubkey.size()); - memcpy(owits[ii][1], pubkey.data(), pubkey.size()); - } - } - *o_wits = owits; -} - -/* Copied from ccan/mem/mem.h which the c++ compiler doesn't like */ -static inline bool memeq(const void *a, size_t al, const void *b, size_t bl) -{ - return al == bl && !memcmp(a, b, bl); -} - -} /* end namespace */ - -extern "C" { -const char *proxy_last_message(void) -{ - return last_message.c_str(); -} - -void proxy_setup() -{ - const char *endpointvar = getenv("REMOTE_HSMD_ENDPOINT"); - const char *endpoint = endpointvar != NULL ? endpointvar : "localhost:50051"; - STATUS_DEBUG("%s:%d %s pid:%d endpoint:%s", - __FILE__, __LINE__, __FUNCTION__, getpid(), endpoint); - auto channel = grpc::CreateChannel(endpoint, - grpc::InsecureChannelCredentials()); - stub = Signer::NewStub(channel); - last_message = ""; -} - -void proxy_set_node_id(const struct node_id *node_id) -{ - self_id = *node_id; -} - -proxy_stat proxy_init_hsm(struct bip32_key_version *bip32_key_version, - struct chainparams const *chainparams, - bool coldstart, - struct secret *hsm_secret, - struct node_id *o_node_id) -{ - STATUS_DEBUG( - "%s:%d %s { \"network\":%s, \"hsm_secret\":%s, \"coldstart\":%s }", - __FILE__, __LINE__, __FUNCTION__, - chainparams->network_name, - hsm_secret != NULL ? dump_secret(hsm_secret).c_str() : "\"\"", - coldstart ? "true" : "false" - ); - - last_message = ""; - InitRequest req; - - auto nc = req.mutable_node_config(); - nc->set_key_derivation_style(NodeConfig::NATIVE); - - auto cp = req.mutable_chainparams(); - cp->set_network_name(chainparams->network_name); - - req.set_coldstart(coldstart); - - // If we are running integration tests the secret will be forced. - if (hsm_secret != NULL) - marshal_bip32seed(hsm_secret, req.mutable_hsm_secret()); - - ClientContext context; - InitReply rsp; - Status status = stub->Init(&context, req, &rsp); - if (status.ok()) { - unmarshal_node_id(rsp.node_id(), o_node_id); - unmarshal_node_id(rsp.node_id(), &self_id); - STATUS_DEBUG("%s:%d %s { \"node_id\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(o_node_id).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: %s", - __FILE__, __LINE__, __FUNCTION__, - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_get_ext_pub_key(struct ext_key *o_ext_pubkey) -{ - // TODO - STATUS_DEBUG("%s:%d %s", __FILE__, __LINE__, __FUNCTION__); - - last_message = ""; - GetExtPubKeyRequest req; - - marshal_node_id(&self_id, req.mutable_node_id()); - - ClientContext context; - GetExtPubKeyReply rsp; - Status status = stub->GetExtPubKey(&context, req, &rsp); - if (status.ok()) { - unmarshal_ext_pubkey(rsp.xpub(), o_ext_pubkey); - STATUS_DEBUG("%s:%d %s " - "{ \"ext_pubkey\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_ext_pubkey(o_ext_pubkey).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: %s", - __FILE__, __LINE__, __FUNCTION__, - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_ecdh(const struct pubkey *point, - struct secret *o_ss) -{ - STATUS_DEBUG( - "%s:%d %s { \"self_id\":%s, \"point\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_pubkey(point).c_str() - ); - - last_message = ""; - ECDHRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - marshal_pubkey(point, req.mutable_point()); - - ClientContext context; - ECDHReply rsp; - Status status = stub->ECDH(&context, req, &rsp); - if (status.ok()) { - unmarshal_secret(rsp.shared_secret(), o_ss); - STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"ss\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_secret(o_ss).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_pass_client_hsmfd( - struct node_id *peer_id, - u64 dbid, - u64 capabilities) -{ - STATUS_DEBUG( - "%s:%d %s " - "{ \"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " - "\"capabilities\":%" PRIu64 " }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_node_id(peer_id).c_str(), - dbid, - capabilities - ); - -/* We used to synthesize NewChannel here, but now have an explicit - * interface. This whole method can go away. */ -#if 0 - last_message = ""; - NewChannelRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); - - ClientContext context; - NewChannelReply rsp; - Status status = stub->NewChannel(&context, req, &rsp); - if (status.ok()) { - STATUS_DEBUG("%s:%d %s { \"self_id\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -#else - last_message = "success"; - return PROXY_OK; -#endif -} - -proxy_stat proxy_handle_new_channel( - struct node_id *peer_id, - u64 dbid) -{ - STATUS_DEBUG( - "%s:%d %s " - "{ \"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 " }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_node_id(peer_id).c_str(), - dbid); - - last_message = ""; - NewChannelRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce0()); - - ClientContext context; - NewChannelReply rsp; - Status status = stub->NewChannel(&context, req, &rsp); - if (status.ok()) { - STATUS_DEBUG("%s:%d %s { \"self_id\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_ready_channel( - struct node_id *peer_id, - u64 dbid, - bool is_outbound, - struct amount_sat *channel_value, - struct amount_msat *push_value, - struct bitcoin_txid *funding_txid, - u16 funding_txout, - u16 holder_to_self_delay, - u8 *holder_shutdown_script, - u32 *holder_shutdown_wallet_index, - struct basepoints *counterparty_basepoints, - struct pubkey *counterparty_funding_pubkey, - u16 counterparty_to_self_delay, - u8 *counterparty_shutdown_script, - struct channel_type *channel_type) -{ - bool option_static_remotekey = channel_type_has(channel_type, OPT_STATIC_REMOTEKEY); - bool option_anchor_outputs = channel_type_has(channel_type, OPT_ANCHOR_OUTPUTS); - - STATUS_DEBUG( - "%s:%d %s { " - "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " - "\"is_outbound\":%s, \"channel_value\":%" PRIu64 ", " - "\"push_value\":%" PRIu64 ", " - "\"funding_txid\":%s, \"funding_txout\":%d, " - "\"holder_to_self_delay\":%d, " - "\"holder_shutdown_script\":%s, " - "\"holder_shutdown_wallet_index\":%s, " - "\"counterparty_basepoints\":%s, " - "\"counterparty_funding_pubkey\":%s, " - "\"counterparty_to_self_delay\":%d, " - "\"counterparty_shutdown_script\":%s, " - "\"option_static_remotekey\":%s, " - "\"option_anchor_outputs\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_node_id(peer_id).c_str(), - dbid, - (is_outbound ? "true" : "false"), - channel_value->satoshis, - push_value->millisatoshis, - dump_bitcoin_txid(funding_txid).c_str(), - funding_txout, - holder_to_self_delay, - dump_hex(holder_shutdown_script, - tal_count(holder_shutdown_script)).c_str(), - dump_optional_wallet_index(holder_shutdown_wallet_index).c_str(), - dump_basepoints(counterparty_basepoints).c_str(), - dump_pubkey(counterparty_funding_pubkey).c_str(), - counterparty_to_self_delay, - dump_hex(counterparty_shutdown_script, - tal_count(counterparty_shutdown_script)).c_str(), - (option_static_remotekey ? "true" : "false"), - (option_anchor_outputs ? "true" : "false") - ); - - last_message = ""; - ReadyChannelRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce0()); - req.set_is_outbound(is_outbound); - req.set_channel_value_sat(channel_value->satoshis); - req.set_push_value_msat(push_value->millisatoshis); - marshal_outpoint(funding_txid, - funding_txout, req.mutable_funding_outpoint()); - req.set_holder_selected_contest_delay(holder_to_self_delay); - marshal_script(holder_shutdown_script, - req.mutable_holder_shutdown_script()); - if (holder_shutdown_wallet_index != NULL) - req.add_holder_shutdown_key_path(*holder_shutdown_wallet_index); - marshal_basepoints(counterparty_basepoints, counterparty_funding_pubkey, - req.mutable_counterparty_basepoints()); - req.set_counterparty_selected_contest_delay(counterparty_to_self_delay); - marshal_script(counterparty_shutdown_script, - req.mutable_counterparty_shutdown_script()); - if (option_anchor_outputs) - req.set_commitment_type(ReadyChannelRequest_CommitmentType_ANCHORS); - else if (option_static_remotekey) - req.set_commitment_type(ReadyChannelRequest_CommitmentType_STATIC_REMOTEKEY); - else - req.set_commitment_type(ReadyChannelRequest_CommitmentType_LEGACY); - - ClientContext context; - ReadyChannelReply rsp; - Status status = stub->ReadyChannel(&context, req, &rsp); - if (status.ok()) { - STATUS_DEBUG("%s:%d %s { \"self_id\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_sign_withdrawal_tx( - struct bitcoin_tx_output **outputs, - struct utxo **utxos, - struct wally_psbt *psbt, - u8 ****o_wits) -{ - STATUS_DEBUG( - "%s:%d %s { " - "\"self_id\":%s, " - "\"utxos\":%s, \"outputs\":%s, \"psbt\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_utxos((const struct utxo **)utxos).c_str(), - dump_bitcoin_tx_outputs( - (const struct bitcoin_tx_output **)outputs).c_str(), - dump_wally_psbt(psbt).c_str() - ); - - last_message = ""; - - // This code is mimicking psbt_txid at bitcoin/psbt.c:796: - // - /* You can *almost* take txid of global tx. But @niftynei thought - * about this far more than me and pointed out that P2SH - * inputs would not be represented, so here we go. */ - struct wally_tx *tx; - tal_wally_start(); - wally_tx_clone_alloc(psbt->tx, 0, &tx); - for (size_t i = 0; i < tx->num_inputs; i++) { - if (psbt->inputs[i].final_scriptsig) { - wally_tx_set_input_script(tx, i, - psbt->inputs[i].final_scriptsig, - psbt->inputs[i].final_scriptsig_len); - } else if (psbt->inputs[i].redeem_script) { - u8 *script; - - /* P2SH requires push of the redeemscript, from libwally src */ - script = tal_arr(tmpctx, u8, 0); - script_push_bytes(&script, - psbt->inputs[i].redeem_script, - psbt->inputs[i].redeem_script_len); - wally_tx_set_input_script(tx, i, script, tal_bytelen(script)); - } - } - tal_wally_end(tal_steal(psbt, tx)); - - - SignOnchainTxRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - - // Serialize the tx we modified above which includes witscripts. - req.mutable_tx()->set_raw_tx_bytes(serialized_wtx(tx, true)); - - assert(psbt->tx->num_inputs >= tal_count(utxos)); - size_t uu = 0; - for (size_t ii = 0; ii < psbt->tx->num_inputs; ++ii) { - InputDescriptor *idesc = req.mutable_tx()->add_input_descs(); - if (uu < tal_count(utxos) && - wally_tx_input_spends(&psbt->tx->inputs[ii], - &utxos[uu]->outpoint)) { - marshal_utxo(utxos[uu], idesc); - ++uu; - } - } - assert(uu == tal_count(utxos)); - - for (size_t ii = 0; ii < psbt->tx->num_outputs; ++ii) { - OutputDescriptor *odesc = req.mutable_tx()->add_output_descs(); - struct wally_map *mp = &psbt->outputs[ii].keypaths; - if (mp->num_items == 1) { - const struct wally_map_item *ip = &mp->items[0]; - size_t npath = - (ip->value_len - BIP32_KEY_FINGERPRINT_LEN) / sizeof(uint32_t); - uint32_t *path = (uint32_t *) (ip->value + BIP32_KEY_FINGERPRINT_LEN); - for (size_t jj = 0; jj < npath; ++jj) { - odesc->mutable_key_loc()->add_key_path(le32_to_cpu(path[jj])); - } - } - } - - ClientContext context; - SignOnchainTxReply rsp; - Status status = stub->SignOnchainTx(&context, req, &rsp); - if (status.ok()) { - unmarshal_witnesses(rsp.witnesses(), o_wits); - STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"witnesses\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_witnesses((u8 const ***) *o_wits).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_sign_remote_commitment_tx( - struct bitcoin_tx *tx, - const struct pubkey *counterparty_funding_pubkey, - struct node_id *peer_id, - u64 dbid, - const struct pubkey *remote_per_commit, - struct simple_htlc **htlcs, - u64 commit_num, u32 feerate, - struct bitcoin_signature *o_sig) -{ - STATUS_DEBUG( - "%s:%d %s { " - "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " - "\"counterparty_funding_pubkey\":%s, " - "\"remote_per_commit\":%s, \"tx\":%s, " - "\"htlcs\":%s, " - "\"commit_num\":%" PRIu64 ", " - "\"feerate\":%d }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_node_id(peer_id).c_str(), - dbid, - dump_pubkey(counterparty_funding_pubkey).c_str(), - dump_pubkey(remote_per_commit).c_str(), - dump_tx(tx).c_str(), - dump_htlcs((const struct simple_htlc **) htlcs, tal_count(htlcs)).c_str(), - commit_num, feerate - ); - - last_message = ""; - SignCounterpartyCommitmentTxRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); - marshal_pubkey(remote_per_commit, - req.mutable_remote_per_commit_point()); - marshal_single_input_tx(tx, NULL, req.mutable_tx()); - for (size_t ii = 0; ii < tal_count(htlcs); ++ii) { - if (htlcs[ii]->side == REMOTE) { - marshal_htlc(htlcs[ii], req.add_offered_htlcs()); - } else { - marshal_htlc(htlcs[ii], req.add_received_htlcs()); - } - } - req.set_commit_num(commit_num); - req.set_feerate_sat_per_kw(feerate); - - ClientContext context; - SignatureReply rsp; - Status status = stub->SignCounterpartyCommitmentTx(&context, req, &rsp); - if (status.ok()) { - unmarshal_bitcoin_signature(rsp.signature(), o_sig); - STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_bitcoin_signature(o_sig).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_get_per_commitment_point( - struct node_id *peer_id, - u64 dbid, - u64 n, - struct pubkey *o_per_commitment_point, - struct secret **o_old_secret) -{ - STATUS_DEBUG("%s:%d %s { " - "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " - "\"n\":%" PRIu64 " }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_node_id(peer_id).c_str(), - dbid, - n - ); - - last_message = ""; - GetPerCommitmentPointRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); - req.set_n(n); - - ClientContext context; - GetPerCommitmentPointReply rsp; - Status status = stub->GetPerCommitmentPoint(&context, req, &rsp); - if (status.ok()) { - unmarshal_pubkey(rsp.per_commitment_point(), - o_per_commitment_point); - if (rsp.old_secret().data().empty()) { - *o_old_secret = NULL; - } else { - *o_old_secret = tal_arr(tmpctx, struct secret, 1); - unmarshal_secret(rsp.old_secret(), *o_old_secret); - } - STATUS_DEBUG("%s:%d %s { " - "\"self_id\":%s, " - "\"per_commitment_point\":%s, " - "\"old_secret\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_pubkey(o_per_commitment_point).c_str(), - (*o_old_secret ? - dump_secret(*o_old_secret).c_str() : "")); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_sign_invoice( - u5 *u5bytes, - u8 *hrpu8, - secp256k1_ecdsa_recoverable_signature *o_sig) -{ - STATUS_DEBUG("%s:%d %s { " - "\"self_id\":%s, \"u5bytes\":%s \"hrpu8\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_hex(u5bytes, tal_count(u5bytes)).c_str(), - string((const char *)hrpu8, tal_count(hrpu8)).c_str() - ); - - last_message = ""; - SignInvoiceRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - req.set_data_part(u5bytes, tal_count(u5bytes)); - req.set_human_readable_part((const char *)hrpu8, tal_count(hrpu8)); - - ClientContext context; - RecoverableNodeSignatureReply rsp; - Status status = stub->SignInvoice(&context, req, &rsp); - if (status.ok()) { - unmarshal_ecdsa_recoverable_signature(rsp.signature(), o_sig); - STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_secp256k1_ecdsa_recoverable_signature( - o_sig).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_sign_message( - u8 *msg, - secp256k1_ecdsa_recoverable_signature *o_sig) -{ - STATUS_DEBUG( - "%s:%d %s { \"self_id\":%s, \"msg\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_hex(msg, tal_count(msg)).c_str() - ); - - last_message = ""; - SignMessageRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - req.set_message(msg, tal_count(msg)); - - ClientContext context; - RecoverableNodeSignatureReply rsp; - Status status = stub->SignMessage(&context, req, &rsp); - if (status.ok()) { - unmarshal_ecdsa_recoverable_signature(rsp.signature(), o_sig); - STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_secp256k1_ecdsa_recoverable_signature( - o_sig).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_channel_update_sig( - u8 *channel_update, - secp256k1_ecdsa_signature *o_sig) -{ - STATUS_DEBUG("%s:%d %s { " - "\"self_id\":%s, \"channel_update\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_hex(channel_update, tal_count(channel_update)).c_str()); - - /* Skip the portion of the channel_update that we don't sign */ - size_t offset = 2 + 64; /* sizeof(type) + sizeof(signature) */ - size_t annsz = tal_count(channel_update); - - last_message = ""; - SignChannelUpdateRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - req.set_channel_update(channel_update + offset, annsz - offset); - - ClientContext context; - NodeSignatureReply rsp; - Status status = stub->SignChannelUpdate(&context, req, &rsp); - if (status.ok()) { - unmarshal_ecdsa_signature(rsp.signature(), o_sig); - STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_secp256k1_ecdsa_signature(o_sig).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_get_channel_basepoints( - struct node_id *peer_id, - u64 dbid, - struct basepoints *o_basepoints, - struct pubkey *o_funding_pubkey) -{ - STATUS_DEBUG("%s:%d %s { " - "\"self_id\":%s, \"peer_id\":%s \"dbid\":%" PRIu64 " }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_node_id(peer_id).c_str(), - dbid - ); - - last_message = ""; - GetChannelBasepointsRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); - - ClientContext context; - GetChannelBasepointsReply rsp; - Status status = stub->GetChannelBasepoints(&context, req, &rsp); - if (status.ok()) { - Basepoints const & bps = rsp.basepoints(); - unmarshal_pubkey(bps.revocation(), &o_basepoints->revocation); - unmarshal_pubkey(bps.payment(), &o_basepoints->payment); - unmarshal_pubkey(bps.htlc(), &o_basepoints->htlc); - unmarshal_pubkey(bps.delayed_payment(), - &o_basepoints->delayed_payment); - unmarshal_pubkey(bps.funding_pubkey(), o_funding_pubkey); - STATUS_DEBUG("%s:%d %s { " - "\"self_id\":%s, \"basepoints\":%s, " - "\"pubkey\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_basepoints(o_basepoints).c_str(), - dump_pubkey(o_funding_pubkey).c_str()); - - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_sign_mutual_close_tx( - struct bitcoin_tx *tx, - const struct pubkey *counterparty_funding_pubkey, - struct node_id *peer_id, - u64 dbid, - struct bitcoin_signature *o_sig) -{ - STATUS_DEBUG( - "%s:%d %s { " - "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " - "\"counterparty_funding_pubkey\":%s, \"tx\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_node_id(peer_id).c_str(), - dbid, - dump_pubkey(counterparty_funding_pubkey).c_str(), - dump_tx(tx).c_str() - ); - - last_message = ""; - SignMutualCloseTxRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); - marshal_single_input_tx(tx, NULL, req.mutable_tx()); - - ClientContext context; - SignatureReply rsp; - Status status = stub->SignMutualCloseTx(&context, req, &rsp); - if (status.ok()) { - unmarshal_bitcoin_signature(rsp.signature(), o_sig); - STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_bitcoin_signature(o_sig).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_sign_commitment_tx( - struct node_id *peer_id, - u64 dbid, - u64 commit_num, - struct bitcoin_signature *o_sig) -{ - STATUS_DEBUG( - "%s:%d %s { " - "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " - "\"commit_num\":%" PRIu64 " }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_node_id(peer_id).c_str(), - dbid, - commit_num - ); - - last_message = ""; - SignHolderCommitmentTxPhase2Request req; - marshal_node_id(&self_id, req.mutable_node_id()); - marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); - req.set_commit_num(commit_num); - - ClientContext context; - CommitmentTxSignatureReply rsp; - Status status = stub->SignHolderCommitmentTxPhase2(&context, req, &rsp); - if (status.ok()) { - unmarshal_bitcoin_signature(rsp.signature(), o_sig); - // NOTE - ignoring rsp.htlc_signatures - STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_bitcoin_signature(o_sig).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_validate_commitment_tx( - struct bitcoin_tx *tx, - struct node_id *peer_id, - u64 dbid, - struct simple_htlc **htlcs, - u64 commit_num, u32 feerate, - struct bitcoin_signature *commit_sig, - struct bitcoin_signature *htlc_sigs, - struct secret **o_old_secret, - struct pubkey *o_next_per_commitment_point) -{ - STATUS_DEBUG( - "%s:%d %s { " - "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " - "\"tx\":%s, " - "\"htlcs\":%s, " - "\"commit_num\":%" PRIu64 ", " - "\"feerate\":%d, " - "\"commit_sig\":%s, \"htlc_sigs\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_node_id(peer_id).c_str(), - dbid, - dump_tx(tx).c_str(), - dump_htlcs((const struct simple_htlc **) htlcs, tal_count(htlcs)).c_str(), - commit_num, feerate, - dump_bitcoin_signature(commit_sig).c_str(), - dump_htlc_signatures(htlc_sigs).c_str() - ); - - last_message = ""; - ValidateHolderCommitmentTxRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); - marshal_single_input_tx(tx, NULL, req.mutable_tx()); - for (size_t ii = 0; ii < tal_count(htlcs); ++ii) { - if (htlcs[ii]->side == LOCAL) { - marshal_htlc(htlcs[ii], req.add_offered_htlcs()); - } else { - marshal_htlc(htlcs[ii], req.add_received_htlcs()); - } - } - req.set_commit_num(commit_num); - req.set_feerate_sat_per_kw(feerate); - marshal_bitcoin_signature(commit_sig, req.mutable_commit_signature()); - for (size_t ii = 0; ii < tal_count(htlc_sigs); ++ii) { - marshal_bitcoin_signature(&htlc_sigs[ii], req.add_htlc_signatures()); - } - - ClientContext context; - ValidateHolderCommitmentTxReply rsp; - Status status = stub->ValidateHolderCommitmentTx(&context, req, &rsp); - if (status.ok()) { - unmarshal_pubkey(rsp.next_per_commitment_point(), o_next_per_commitment_point); - if (rsp.old_secret().data().empty()) { - *o_old_secret = NULL; - } else { - *o_old_secret = tal_arr(tmpctx, struct secret, 1); - unmarshal_secret(rsp.old_secret(), *o_old_secret); - } - STATUS_DEBUG("%s:%d %s { " - "\"self_id\":%s, " - "\"next_per_commitment_point\":%s, " - "\"old_secret\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_pubkey(o_next_per_commitment_point).c_str(), - (*o_old_secret ? - dump_secret(*o_old_secret).c_str() : "")); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_validate_revocation( - struct node_id *peer_id, - u64 dbid, - u64 revoke_num, - struct secret *old_secret) -{ - STATUS_DEBUG( - "%s:%d %s { " - "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " - "\"revoke_num\":%" PRIu64 ", " - "\"old_secret\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_node_id(peer_id).c_str(), - dbid, - revoke_num, - dump_secret(old_secret).c_str() - ); - - last_message = ""; - ValidateCounterpartyRevocationRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); - req.set_revoke_num(revoke_num); - marshal_secret(old_secret, req.mutable_old_secret()); - - ClientContext context; - ValidateCounterpartyRevocationReply rsp; - Status status = stub->ValidateCounterpartyRevocation(&context, req, &rsp); - if (status.ok()) { - STATUS_DEBUG("%s:%d %s { " - "\"self_id\":%s } ", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_cannouncement_sig( - struct node_id *peer_id, - u64 dbid, - u8 *channel_announcement, - secp256k1_ecdsa_signature *o_node_sig, - secp256k1_ecdsa_signature *o_bitcoin_sig) -{ - STATUS_DEBUG( - "%s:%d %s { " - "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " - "\"ca\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_node_id(peer_id).c_str(), - dbid, - dump_hex(channel_announcement, - tal_count(channel_announcement)).c_str() - ); - - /* Skip the portion of the channel_update that we don't sign */ - size_t offset = 2 + 256; /* sizeof(type) + 4*sizeof(signature) */ - size_t annsz = tal_count(channel_announcement); - - last_message = ""; - SignChannelAnnouncementRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); - req.set_channel_announcement(channel_announcement + offset, - annsz - offset); - - ClientContext context; - SignChannelAnnouncementReply rsp; - Status status = stub->SignChannelAnnouncement(&context, req, &rsp); - if (status.ok()) { - unmarshal_ecdsa_signature(rsp.node_signature(), o_node_sig); - unmarshal_ecdsa_signature(rsp.bitcoin_signature(), o_bitcoin_sig); - STATUS_DEBUG("%s:%d %s { " - "\"self_id\":%s, \"node_sig\":%s, " - "\"bitcoin_sig\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_secp256k1_ecdsa_signature(o_node_sig).c_str(), - dump_secp256k1_ecdsa_signature(o_bitcoin_sig).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_sign_local_htlc_tx( - struct bitcoin_tx *tx, - u64 commit_num, - u8 *wscript, - struct node_id *peer_id, - u64 dbid, - struct bitcoin_signature *o_sig) -{ - STATUS_DEBUG( - "%s:%d %s { " - "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " - "\"commit_num\":%" PRIu64 ", \"wscript\":%s, \"tx\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_node_id(peer_id).c_str(), - dbid, - commit_num, - dump_hex(wscript, tal_count(wscript)).c_str(), - dump_tx(tx).c_str() - ); - - last_message = ""; - SignHolderHTLCTxRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); - req.set_n(commit_num); - marshal_single_input_tx(tx, wscript, req.mutable_tx()); - - ClientContext context; - SignatureReply rsp; - Status status = stub->SignHolderHTLCTx(&context, req, &rsp); - if (status.ok()) { - unmarshal_bitcoin_signature(rsp.signature(), o_sig); - STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_bitcoin_signature(o_sig).c_str() - ); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_sign_remote_htlc_tx( - struct bitcoin_tx *tx, - u8 *wscript, - const struct pubkey *remote_per_commit_point, - struct node_id *peer_id, - u64 dbid, - struct bitcoin_signature *o_sig) -{ - STATUS_DEBUG("%s:%d %s { " - "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " - "\"wscript\":%s, \"tx\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_node_id(peer_id).c_str(), - dbid, - dump_hex(wscript, tal_count(wscript)).c_str(), - dump_tx(tx).c_str() - ); - - last_message = ""; - SignCounterpartyHTLCTxRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); - marshal_pubkey(remote_per_commit_point, - req.mutable_remote_per_commit_point()); - marshal_single_input_tx(tx, wscript, req.mutable_tx()); - - ClientContext context; - SignatureReply rsp; - Status status = stub->SignCounterpartyHTLCTx(&context, req, &rsp); - if (status.ok()) { - unmarshal_bitcoin_signature(rsp.signature(), o_sig); - STATUS_DEBUG("%s:%d %s { \"self_id\":%s. \"sig\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_bitcoin_signature(o_sig).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_sign_delayed_payment_to_us( - struct bitcoin_tx *tx, - u64 commit_num, - u8 *wscript, - struct node_id *peer_id, - u64 dbid, - struct bitcoin_signature *o_sig) -{ - STATUS_DEBUG("%s:%d %s { " - "\"self_id\":%s, \"peer_id\":%s, dbid=%" PRIu64 ", " - "\"commit_num\":=%" PRIu64 ", " - "\"wscript\":%s, \"tx\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_node_id(peer_id).c_str(), - dbid, - commit_num, - dump_hex(wscript, tal_count(wscript)).c_str(), - dump_tx(tx).c_str() - ); - - last_message = ""; - SignDelayedSweepRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); - req.set_input(0); - req.set_commitment_number(commit_num); - marshal_single_input_tx(tx, wscript, req.mutable_tx()); - - ClientContext context; - SignatureReply rsp; - Status status = stub->SignDelayedSweep(&context, req, &rsp); - if (status.ok()) { - unmarshal_bitcoin_signature(rsp.signature(), o_sig); - STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_bitcoin_signature(o_sig).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_sign_remote_htlc_to_us( - struct bitcoin_tx *tx, - u8 *wscript, - const struct pubkey *remote_per_commit_point, - struct node_id *peer_id, - u64 dbid, - struct bitcoin_signature *o_sig) -{ - STATUS_DEBUG("%s:%d %s { " - "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " - "\"wscript\":%s, \"tx\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_node_id(peer_id).c_str(), - dbid, - dump_hex(wscript, tal_count(wscript)).c_str(), - dump_tx(tx).c_str() - ); - - last_message = ""; - SignCounterpartyHTLCSweepRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); - req.set_input(0); - marshal_pubkey(remote_per_commit_point, - req.mutable_remote_per_commit_point()); - marshal_single_input_tx(tx, wscript, req.mutable_tx()); - - ClientContext context; - SignatureReply rsp; - Status status = stub->SignCounterpartyHTLCSweep(&context, req, &rsp); - if (status.ok()) { - unmarshal_bitcoin_signature(rsp.signature(), o_sig); - STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_bitcoin_signature(o_sig).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_sign_penalty_to_us( - struct bitcoin_tx *tx, - struct secret *revocation_secret, - u8 *wscript, - struct node_id *peer_id, - u64 dbid, - struct bitcoin_signature *o_sig) -{ - STATUS_DEBUG( - "%s:%d %s { " - "\"self_id\":%s, \"peer_id\":%s, \"dbid\":%" PRIu64 ", " - "\"revocation_secret\":%s, \"wscript\":%s, \"tx\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_node_id(peer_id).c_str(), - dbid, - dump_hex(revocation_secret->data, - sizeof(revocation_secret->data)).c_str(), - dump_hex(wscript, tal_count(wscript)).c_str(), - dump_tx(tx).c_str() - ); - - last_message = ""; - SignJusticeSweepRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); - marshal_secret(revocation_secret, req.mutable_revocation_secret()); - req.set_input(0); - marshal_single_input_tx(tx, wscript, req.mutable_tx()); - - ClientContext context; - SignatureReply rsp; - Status status = stub->SignJusticeSweep(&context, req, &rsp); - if (status.ok()) { - unmarshal_bitcoin_signature(rsp.signature(), o_sig); - STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_bitcoin_signature(o_sig).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_check_future_secret( - struct node_id *peer_id, - u64 dbid, - u64 n, - struct secret *suggested, - bool *o_correct) -{ - STATUS_DEBUG( - "%s:%d %s { \"self_id\":%s, \"peer_id\":%s, " - "\"dbid\":%" PRIu64 ", " - "\"n\":%" PRIu64 ", \"suggested\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_node_id(peer_id).c_str(), - dbid, - n, - dump_hex(suggested->data, sizeof(suggested->data)).c_str() - ); - - last_message = ""; - CheckFutureSecretRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - marshal_channel_nonce(peer_id, dbid, req.mutable_channel_nonce()); - req.set_n(n); - marshal_secret(suggested, req.mutable_suggested()); - - ClientContext context; - CheckFutureSecretReply rsp; - Status status = stub->CheckFutureSecret(&context, req, &rsp); - if (status.ok()) { - *o_correct = rsp.correct(); - STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"correct\":%d }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), int(*o_correct)); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -proxy_stat proxy_handle_sign_node_announcement( - u8 *node_announcement, - secp256k1_ecdsa_signature *o_sig) -{ - STATUS_DEBUG( - "%s:%d %s { \"self_id\":%s, \"ann\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_hex(node_announcement, - tal_count(node_announcement)).c_str() - ); - - /* Skip the portion of the channel_update that we don't sign */ - size_t offset = 2 + 64; /* sizeof(type) + sizeof(signature) */ - size_t annsz = tal_count(node_announcement); - - last_message = ""; - SignNodeAnnouncementRequest req; - marshal_node_id(&self_id, req.mutable_node_id()); - req.set_node_announcement(node_announcement + offset, annsz - offset); - - ClientContext context; - NodeSignatureReply rsp; - Status status = stub->SignNodeAnnouncement(&context, req, &rsp); - if (status.ok()) { - unmarshal_ecdsa_signature(rsp.signature(), o_sig); - STATUS_DEBUG("%s:%d %s { \"self_id\":%s, \"sig\":%s }", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - dump_secp256k1_ecdsa_signature(o_sig).c_str()); - last_message = "success"; - return PROXY_OK; - } else { - status_unusual("%s:%d %s: self_id=%s %s", - __FILE__, __LINE__, __FUNCTION__, - dump_node_id(&self_id).c_str(), - status.error_message().c_str()); - last_message = status.error_message(); - return map_status(status); - } -} - -// FIXME - These routines allows us to pretty print to stderr from C -// code. Probably should remove it in production ... - -void print_tx(char const *tag, struct bitcoin_tx const *tx) -{ - fprintf(stderr, "%s: bitcoin_tx=%s\n", tag, dump_tx(tx).c_str()); -} - -void print_psbt(char const *tag, const struct wally_psbt *psbt) -{ - fprintf(stderr, "%s: wally_psbt=%s\n", - tag, dump_wally_psbt(psbt).c_str()); -} - -} /* extern "C" */ diff --git a/contrib/remote_hsmd/proxy.hpp b/contrib/remote_hsmd/proxy.hpp deleted file mode 100644 index cbfd3cfcbba4..000000000000 --- a/contrib/remote_hsmd/proxy.hpp +++ /dev/null @@ -1,231 +0,0 @@ -#ifndef LIGHTNING_CONTRIB_REMOTE_HSMD_PROXY_H -#define LIGHTNING_CONTRIB_REMOTE_HSMD_PROXY_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - -struct bip32_key_version; -struct channel_type; -struct utxo; -struct simple_htlc; - -#define STATUS_DEBUG(args...) \ - do { \ - fprintf(stderr, args); \ - fprintf(stderr, "\n"); \ - } while (false) - -struct bip32_key_version; -struct utxo; -struct witscript; - -enum proxy_status { - /* SUCCESS */ - PROXY_OK = 0, - - /* TRANSIENT */ - PROXY_TIMEOUT = 32, - PROXY_UNAVAILABLE = 33, - PROXY_CANCELLED = 34, - - /* PERMANENT */ - PROXY_INVALID_ARGUMENT = 100, - PROXY_INTERNAL_ERROR = 200, -}; -typedef enum proxy_status proxy_stat; - -#define PROXY_SUCCESS(rv) ((rv) < 32) -#define PROXY_TRANSIENT(rv) ((rv) >= 32 && (rv) < 100) -#define PROXY_PERMANENT(rv) ((rv) >= 100) - -char const *proxy_last_message(void); - -void proxy_setup(void); - -void proxy_set_node_id(const struct node_id *node_id); - -proxy_stat proxy_init_hsm( - struct bip32_key_version *bip32_key_version, - struct chainparams const *chainparams, - bool coldstart, - struct secret *hsm_secret, - struct node_id *o_node_id); - -proxy_stat proxy_get_ext_pub_key(struct ext_key *o_ext_pub_key); - -proxy_stat proxy_handle_ecdh( - const struct pubkey *point, - struct secret *o_ss); - -proxy_stat proxy_handle_pass_client_hsmfd( - struct node_id *peer_id, - u64 dbid, - u64 capabilities); - -proxy_stat proxy_handle_new_channel( - struct node_id *peer_id, - u64 dbid); - -proxy_stat proxy_handle_ready_channel( - struct node_id *peer_id, - u64 dbid, - bool is_outbound, - struct amount_sat *channel_value, - struct amount_msat *push_value, - struct bitcoin_txid *funding_txid, - u16 funding_txout, - u16 local_to_self_delay, - u8 *local_shutdown_script, - u32 *local_shutdown_wallet_index, - struct basepoints *remote_basepoints, - struct pubkey *remote_funding_pubkey, - u16 remote_to_self_delay, - u8 *remote_shutdown_script, - struct channel_type *channel_type); - -proxy_stat proxy_handle_sign_withdrawal_tx( - struct bitcoin_tx_output **outputs, - struct utxo **utxos, - struct wally_psbt *psbt, - u8 ****o_wits); - -proxy_stat proxy_handle_sign_remote_commitment_tx( - struct bitcoin_tx *tx, - const struct pubkey *remote_funding_pubkey, - struct node_id *peer_id, - u64 dbid, - const struct pubkey *remote_per_commit, - struct simple_htlc **htlc, - u64 commit_num, - u32 feerate, - struct bitcoin_signature *o_sig); - -proxy_stat proxy_handle_get_per_commitment_point( - struct node_id *peer_id, - u64 dbid, - u64 n, - struct pubkey *o_per_commitment_point, - struct secret **o_old_secret); - -proxy_stat proxy_handle_sign_invoice( - u5 *u5bytes, - u8 *hrpu8, - secp256k1_ecdsa_recoverable_signature *o_sig); - -proxy_stat proxy_handle_sign_message( - u8 *msg, - secp256k1_ecdsa_recoverable_signature *o_sig); - -proxy_stat proxy_handle_get_channel_basepoints( - struct node_id *peer_id, - u64 dbid, - struct basepoints *o_basepoints, - struct pubkey *o_funding_pubkey); - -proxy_stat proxy_handle_sign_mutual_close_tx( - struct bitcoin_tx *tx, - const struct pubkey *remote_funding_pubkey, - struct node_id *peer_id, - u64 dbid, - struct bitcoin_signature *o_sig); - -proxy_stat proxy_handle_sign_commitment_tx( - struct node_id *peer_id, - u64 dbid, - u64 commit_num, - struct bitcoin_signature *o_sig); - -proxy_stat proxy_handle_validate_commitment_tx( - struct bitcoin_tx *tx, - struct node_id *peer_id, - u64 dbid, - struct simple_htlc **htlc, - u64 commit_num, - u32 feerate, - struct bitcoin_signature *commit_sig, - struct bitcoin_signature *htlc_sigs, - struct secret **o_old_secret, - struct pubkey *next_per_commitment_point); - -proxy_stat proxy_handle_validate_revocation( - struct node_id *peer_id, - u64 dbid, - u64 revoke_num, - struct secret *old_secret); - -proxy_stat proxy_handle_cannouncement_sig( - struct node_id *peer_id, - u64 dbid, - u8 *channel_announcement, - secp256k1_ecdsa_signature *o_node_sig, - secp256k1_ecdsa_signature *o_bitcoin_sig); - -proxy_stat proxy_handle_channel_update_sig( - u8 *channel_update, - secp256k1_ecdsa_signature *o_sig); - -proxy_stat proxy_handle_sign_local_htlc_tx( - struct bitcoin_tx *tx, - u64 commit_num, - u8 *wscript, - struct node_id *peer_id, - u64 dbid, - struct bitcoin_signature *o_sig); - -proxy_stat proxy_handle_sign_remote_htlc_tx( - struct bitcoin_tx *tx, - u8 *wscript, - const struct pubkey *remote_per_commit_point, - struct node_id *peer_id, - u64 dbid, - struct bitcoin_signature *o_sig); - -proxy_stat proxy_handle_sign_delayed_payment_to_us( - struct bitcoin_tx *tx, - u64 commit_num, - u8 *wscript, - struct node_id *peer_id, - u64 dbid, - struct bitcoin_signature *o_sig); - -proxy_stat proxy_handle_sign_remote_htlc_to_us( - struct bitcoin_tx *tx, - u8 *wscript, - const struct pubkey *remote_per_commit_point, - struct node_id *peer_id, - u64 dbid, - struct bitcoin_signature *o_sig); - -proxy_stat proxy_handle_sign_penalty_to_us( - struct bitcoin_tx *tx, - struct secret *revocation_secret, - u8 *wscript, - struct node_id *peer_id, - u64 dbid, - struct bitcoin_signature *o_sig); - -proxy_stat proxy_handle_check_future_secret( - struct node_id *peer_id, - u64 dbid, - u64 n, - struct secret *suggested, - bool *o_correct); - -proxy_stat proxy_handle_sign_node_announcement( - u8 *node_announcement, - secp256k1_ecdsa_signature *o_sig); - -// FIXME - For debugging, remove for production. -void print_tx(char const *tag, struct bitcoin_tx const *tx); -void print_psbt(char const *tag, const struct wally_psbt *psbt); - -#ifdef __cplusplus -} /* extern C */ -#endif - -#endif /* LIGHTNING_CONTRIB_REMOTE_HSMD_PROXY_H */ diff --git a/contrib/remote_hsmd/scripts/cleanup b/contrib/remote_hsmd/scripts/cleanup deleted file mode 100755 index b618ace616ff..000000000000 --- a/contrib/remote_hsmd/scripts/cleanup +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/sh - -rm -rf /tmp/ltests-* -rm -rf /tmp/lnpt-cl-* -rm -rf /tmp/tmp* -rm -rf /tmp/.tmp* -rm -rf /tmp/vgdb-pipe-* -rm -rf /tmp/git-difftool.* -killall bitcoind -killall vlsd diff --git a/contrib/remote_hsmd/scripts/rerun-failed-tests b/contrib/remote_hsmd/scripts/rerun-failed-tests deleted file mode 100755 index 0e936a4ee776..000000000000 --- a/contrib/remote_hsmd/scripts/rerun-failed-tests +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/sh - -echo "accepts log w/ failures on stdin" - -TESTS=`awk '/^FAILED tests/ {print $2}'` - -# scale the parallelism to number of processors -NPROC=$(nproc) -TPAR=$((NPROC * 2)) - -PYTHONPATH=\ -$PWD/contrib/pyln-client:\ -$PWD/contrib/pyln-testing:\ -$PWD/contrib/pyln-proto \ -TEST_DEBUG=1 \ -DEVELOPER=1 \ -VALGRIND=0 \ -SUBDAEMON='hsmd:remote_hsmd' \ -REMOTE_SIGNER_CMD=$(pwd)/../validating-lightning-signer/target/debug/vlsd \ -REMOTE_SIGNER_ALLOWLIST=$(pwd)/contrib/remote_hsmd/TESTING_ALLOWLIST \ -pytest \ -$TESTS \ --n=$TPAR --timeout=300 --timeout_method=thread -v diff --git a/contrib/remote_hsmd/scripts/run-all-tests b/contrib/remote_hsmd/scripts/run-all-tests deleted file mode 100755 index 144326b2aa91..000000000000 --- a/contrib/remote_hsmd/scripts/run-all-tests +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/sh - -# scale the parallelism to number of processors -JPAR=$(nproc) -TPAR=$((JPAR * 2)) - -SUBDAEMON='hsmd:remote_hsmd' \ -REMOTE_SIGNER_CMD=$(pwd)/../validating-lightning-signer/target/debug/vlsd \ -REMOTE_SIGNER_ALLOWLIST=$(pwd)/contrib/remote_hsmd/TESTING_ALLOWLIST \ -make \ --j$JPAR PYTEST_PAR=$TPAR \ -DEVELOPER=1 \ -VALGRIND=0 \ -SLOW_MACHINE=0 \ -PYTEST_MOREOPTS='--timeout=300 --timeout_method=thread' \ -pytest - diff --git a/contrib/remote_hsmd/scripts/run-one-test b/contrib/remote_hsmd/scripts/run-one-test deleted file mode 100755 index 69feb9192411..000000000000 --- a/contrib/remote_hsmd/scripts/run-one-test +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/sh - -THETEST=$1 - -PYTHONPATH=\ -$PWD/contrib/pyln-client:\ -$PWD/contrib/pyln-testing:\ -$PWD/contrib/pyln-proto \ -TEST_KEEPDIR=1 \ -TEST_DEBUG=1 \ -DEVELOPER=1 \ -VALGRIND=0 \ -SLOW_MACHINE=0 \ -SUBDAEMON='hsmd:remote_hsmd' \ -REMOTE_SIGNER_CMD=$(pwd)/../validating-lightning-signer/target/debug/vlsd \ -REMOTE_SIGNER_ALLOWLIST=$(pwd)/contrib/remote_hsmd/TESTING_ALLOWLIST \ -pytest \ -$THETEST \ --v --timeout=300 --timeout_method=thread -x -s From 9822266f6bf2a11d397b638335b7cb75d34bb7de Mon Sep 17 00:00:00 2001 From: Devrandom Date: Tue, 22 Mar 2022 10:45:42 +0100 Subject: [PATCH 11/53] don't get proto defs from gitlab reverts build.sh to original --- .github/scripts/build.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/scripts/build.sh b/.github/scripts/build.sh index 0771908b3341..1037cc97d8aa 100755 --- a/.github/scripts/build.sh +++ b/.github/scripts/build.sh @@ -31,8 +31,6 @@ poetry install git clone https://github.com/lightning/bolts.git ../${BOLTDIR} git submodule update --init --recursive -wget -q --directory-prefix=contrib/remote_hsmd https://gitlab.com/lightning-signer/validating-lightning-signer/-/raw/master/src/server/remotesigner.proto - ./configure CC="$CC" cat config.vars From ae59b56841e126103e3fc6f2c4459d37174b0942 Mon Sep 17 00:00:00 2001 From: Devrandom Date: Wed, 23 Mar 2022 10:10:40 +0100 Subject: [PATCH 12/53] skip test_invoice_preimage --- tests/test_invoices.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_invoices.py b/tests/test_invoices.py index 83aa56d2d5c5..0c072140ee0f 100644 --- a/tests/test_invoices.py +++ b/tests/test_invoices.py @@ -130,6 +130,7 @@ def test_invoice_weirdstring(node_factory): l1.rpc.delinvoice(weird_label, "unpaid") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "VLS policies catch two-invoice-one-preimage ahead of the node") def test_invoice_preimage(node_factory): """Test explicit invoice 'preimage'. """ From e1c463837049cf2dbb879f3266bc43318080f513 Mon Sep 17 00:00:00 2001 From: Devrandom Date: Wed, 23 Mar 2022 15:06:43 +0100 Subject: [PATCH 13/53] skip test_upgrade_statickey --- tests/test_connection.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_connection.py b/tests/test_connection.py index 65aa39f39a83..e23c6b319e27 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -3717,6 +3717,7 @@ def test_openchannel_init_alternate(node_factory, executor): @unittest.skipIf(not EXPERIMENTAL_FEATURES, "upgrade protocol not available") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "upgrade not yet supported by VLS") @pytest.mark.developer("dev-force-features required") def test_upgrade_statickey(node_factory, executor): """l1 doesn't have option_static_remotekey, l2 offers it.""" @@ -3747,6 +3748,7 @@ def test_upgrade_statickey(node_factory, executor): @unittest.skipIf(not EXPERIMENTAL_FEATURES, "upgrade protocol not available") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "upgrade not yet supported by VLS") @pytest.mark.developer("dev-force-features required") def test_upgrade_statickey_onchaind(node_factory, executor, bitcoind): """We test penalty before/after, and unilateral before/after""" @@ -3881,6 +3883,7 @@ def test_upgrade_statickey_onchaind(node_factory, executor, bitcoind): @unittest.skipIf(not EXPERIMENTAL_FEATURES, "upgrade protocol not available") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "upgrade not yet supported by VLS") @pytest.mark.developer("dev-force-features, dev-disconnect required") def test_upgrade_statickey_fail(node_factory, executor, bitcoind): """We reconnect at all points during retransmit, and we won't upgrade.""" From efdc61c94c122494e1b3041c964d84c333615366 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Thu, 24 Mar 2022 14:18:12 -0700 Subject: [PATCH 14/53] skip test_openchannel_hook_error_handling, frequently hangs --- tests/test_plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 05e0de1ed4db..d69f782ba939 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -689,6 +689,7 @@ def test_openchannel_hook(node_factory, bitcoind): l1.rpc.fundchannel(l2.info['id'], 100001) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "this test frequently hangs w/ VLSD") @pytest.mark.openchannel('v1') @pytest.mark.openchannel('v2') def test_openchannel_hook_error_handling(node_factory, bitcoind): From 4d76ca7e5d66948c2caef6de4803da3a018c01b4 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Thu, 24 Mar 2022 16:39:14 -0700 Subject: [PATCH 15/53] Addded gitgnore of contrib/remote_hsmd because dirty --- contrib/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 contrib/.gitignore diff --git a/contrib/.gitignore b/contrib/.gitignore new file mode 100644 index 000000000000..f44fc41890ac --- /dev/null +++ b/contrib/.gitignore @@ -0,0 +1 @@ +/remote_hsmd From 34cf634cb524c82a834a73549d0d42be14d3334a Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Fri, 25 Mar 2022 17:29:41 -0700 Subject: [PATCH 16/53] ignore remote_hsmd_vls because dirty --- lightningd/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lightningd/.gitignore b/lightningd/.gitignore index 67b3ee6cfd3c..b2f53af9886f 100644 --- a/lightningd/.gitignore +++ b/lightningd/.gitignore @@ -1,4 +1,4 @@ -lightningd +/remote_hsmd_vls lightning_channeld lightning_closingd lightning_connectd @@ -8,4 +8,5 @@ lightning_hsmd lightning_onchaind lightning_openingd lightning_websocketd +lightningd remote_hsmd From 5a15edb6e40678adc3c375d294c12270bd8bb378 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Mon, 28 Mar 2022 22:19:55 -0700 Subject: [PATCH 17/53] unskip bolt12 tests --- tests/test_pay.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/test_pay.py b/tests/test_pay.py index 2b97f8506dd8..978f01aeb9f9 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -4385,7 +4385,6 @@ def test_offer_needs_option(node_factory): assert l1.rpc.decode('lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcgqyqs5pr5v4ehg93pqfnwgkvdr57yzh6h92zg3qctvrm7w38djg67kzcm4yeg8vc4cq63s')['valid'] -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't support bolt12 yet") def test_offer(node_factory, bitcoind): plugin = os.path.join(os.path.dirname(__file__), 'plugins/currencyUSDAUD5000.py') l1 = node_factory.get_node(options={'plugin': plugin, 'experimental-offers': None}) @@ -4564,7 +4563,6 @@ def test_offer_deprecated_api(node_factory, bitcoind): l1.rpc.pay(inv['invoice']) -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't support bolt12 yet") @pytest.mark.developer("dev-no-modern-onion is DEVELOPER-only") def test_fetchinvoice_3hop(node_factory, bitcoind): l1, l2, l3, l4 = node_factory.line_graph(4, wait_for_announce=True, @@ -4578,7 +4576,6 @@ def test_fetchinvoice_3hop(node_factory, bitcoind): l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12']}) -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't support bolt12 yet") def test_fetchinvoice(node_factory, bitcoind): # We remove the conversion plugin on l3, causing it to get upset. l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True, @@ -4799,7 +4796,6 @@ def test_fetchinvoice_recurrence(node_factory, bitcoind): @pytest.mark.developer("Needs dev-allow-localhost for autoconnect, dev-force-features to avoid routing onionmsgs") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't support bolt12 yet") def test_fetchinvoice_autoconnect(node_factory, bitcoind): """We should autoconnect if we need to, to route.""" @@ -4866,7 +4862,6 @@ def test_pay_waitblockheight_timeout(node_factory, bitcoind): @pytest.mark.developer("dev-rawrequest is DEVELOPER-only") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't support bolt12 yet") def test_dev_rawrequest(node_factory): l1, l2 = node_factory.line_graph(2, fundchannel=False, opts={'experimental-offers': None}) From c647880403506cb7a262831304b66acb862ce6f1 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Sun, 6 Mar 2022 22:49:59 -0800 Subject: [PATCH 18/53] Add --rpc argument to connect vlsd to regtest in integration tests --- contrib/pyln-testing/pyln/testing/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contrib/pyln-testing/pyln/testing/utils.py b/contrib/pyln-testing/pyln/testing/utils.py index 0017a6acae65..091630eaabac 100644 --- a/contrib/pyln-testing/pyln/testing/utils.py +++ b/contrib/pyln-testing/pyln/testing/utils.py @@ -592,7 +592,11 @@ def __init__(self, vlsd_dir, vlsd_port): '--port={}'.format(vlsd_port), '--initial-allowlist-file={}'.format(env('REMOTE_SIGNER_ALLOWLIST', 'contrib/remote_hsmd/TESTING_ALLOWLIST')), - ] + '--rpc=http://{}:{}@127.0.0.1:{}'.format( + BITCOIND_CONFIG['rpcuser'], + BITCOIND_CONFIG['rpcpassword'], + BITCOIND_CONFIG['rpcport']), + ] self.prefix = 'vlsd' self.vlsd_port = vlsd_port From 64d1179081d51c1ec1e813372c40e3f240d13b96 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Sun, 6 Mar 2022 22:51:30 -0800 Subject: [PATCH 19/53] WIP: unskipped a reorg test --- tests/test_misc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_misc.py b/tests/test_misc.py index 0789f8b84980..f1fdec3c2c20 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1205,7 +1205,6 @@ def test_daemon_option(node_factory): @pytest.mark.developer("needs DEVELOPER=1") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't have repeatable random seeding") def test_blockchaintrack(node_factory, bitcoind): """Check that we track the blockchain correctly across reorgs """ From dfecf1f88b9ba223862bdbffbcd469711d17688d Mon Sep 17 00:00:00 2001 From: Devrandom Date: Mon, 2 May 2022 17:55:46 +0200 Subject: [PATCH 20/53] testing: allow multiple SUBDAEMON items --- contrib/pyln-testing/pyln/testing/utils.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/contrib/pyln-testing/pyln/testing/utils.py b/contrib/pyln-testing/pyln/testing/utils.py index 091630eaabac..a9673de56753 100644 --- a/contrib/pyln-testing/pyln/testing/utils.py +++ b/contrib/pyln-testing/pyln/testing/utils.py @@ -636,7 +636,7 @@ def __init__( self.cmd_prefix = [] self.disconnect_file = None self.lightning_dir = lightning_dir - self.use_vlsd = False; + self.use_vlsd = False self.vlsd_dir = os.path.join(lightning_dir, "vlsd") self.rpcproxy = bitcoindproxy @@ -661,7 +661,16 @@ def __init__( opts['grpc-port'] = grpc_port if SUBDAEMON: - opts['subdaemon'] = SUBDAEMON + assert node_id > 0 + subdaemons = SUBDAEMON.split(',') + if node_id > len(subdaemons): + # use the last element if not as many specifiers as nodes + opts['subdaemon'] = subdaemons[-1] + else: + # use the matching specifier + opts['subdaemon'] = subdaemons[node_id - 1] + + print(f"starting node {node_id} with subdaemon {opts['subdaemon']}") if SUBDAEMON == 'hsmd:remote_hsmd': self.use_vlsd = True From 1e1ed8b3f6c8b76e0ddb52b88cc2c232d73d8b37 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Mon, 9 May 2022 08:22:32 -0700 Subject: [PATCH 21/53] Skip test with IPv6 issue in CI runner --- tests/test_gossip.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_gossip.py b/tests/test_gossip.py index 9f5dc64c05c3..92d496a419b0 100644 --- a/tests/test_gossip.py +++ b/tests/test_gossip.py @@ -185,6 +185,7 @@ def test_announce_dns_suppressed(node_factory, bitcoind): @pytest.mark.developer("gossip without DEVELOPER=1 is slow") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "trouble with IPv6 in VLS CI runner") def test_announce_and_connect_via_dns(node_factory, bitcoind): """ Test that DNS annoucements propagate and can be used when connecting. From ae5f82ada6b3a5aebb8a3ecaeb62aa96ceb5f77b Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Mon, 9 May 2022 13:51:52 -0700 Subject: [PATCH 22/53] Skip test_multifunding_feerates because flakes too often --- tests/test_connection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_connection.py b/tests/test_connection.py index e23c6b319e27..557af2f8e1eb 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -2068,6 +2068,7 @@ def test_multifunding_wumbo(node_factory): @unittest.skipIf(TEST_NETWORK == 'liquid-regtest', "Fees on elements are different") @pytest.mark.developer("uses dev-fail") @pytest.mark.openchannel('v1') # v2 the weight calculation is off by 3 +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "flakes too frequently w/ VLS") def test_multifunding_feerates(node_factory, bitcoind): ''' Test feerate parameters for multifundchannel From 52fac8d338f57a671df2b2002b048c4d9e783508 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Tue, 10 May 2022 15:19:02 -0700 Subject: [PATCH 23/53] Skip tests which frequently flake --- tests/test_closing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_closing.py b/tests/test_closing.py index 5b0cc883812a..5b30c33c8cf2 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -2831,6 +2831,7 @@ def route_to_l1(src): @pytest.mark.developer("needs DEVELOPER=1 for dev_ignore_htlcs") @pytest.mark.slow_test +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "frequently flakes") def test_onchain_multihtlc_our_unilateral(node_factory, bitcoind): """Node pushes a channel onchain with multiple HTLCs with same payment_hash """ h, l1, l2, l3, l4, l5, l6, l7 = setup_multihtlc_test(node_factory, bitcoind) @@ -2887,6 +2888,7 @@ def test_onchain_multihtlc_our_unilateral(node_factory, bitcoind): @pytest.mark.developer("needs DEVELOPER=1 for dev_ignore_htlcs") @pytest.mark.slow_test +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "frequently flakes") def test_onchain_multihtlc_their_unilateral(node_factory, bitcoind): """Node pushes a channel onchain with multiple HTLCs with same payment_hash """ h, l1, l2, l3, l4, l5, l6, l7 = setup_multihtlc_test(node_factory, bitcoind) From 6642061c53b1042d3554c2d675b26ef8270b541c Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Tue, 24 May 2022 16:27:03 -0700 Subject: [PATCH 24/53] Set BITCOIND_RPC_URL env variable in testing framework --- contrib/pyln-testing/pyln/testing/utils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/contrib/pyln-testing/pyln/testing/utils.py b/contrib/pyln-testing/pyln/testing/utils.py index a9673de56753..778017f5ea5d 100644 --- a/contrib/pyln-testing/pyln/testing/utils.py +++ b/contrib/pyln-testing/pyln/testing/utils.py @@ -736,6 +736,12 @@ def start(self, stdin=None, wait_for_initialized=True, stderr_redir=False): # We can't do this in the constructor because we need a new port on each restart. self.env['REMOTE_HSMD_ENDPOINT'] = '127.0.0.1:{}'.format(vlsd_port) + # Some of the remote hsmd proxies need a bitcoind RPC connection + self.env['BITCOIND_RPC_URL'] = 'http://{}:{}@localhost:{}'.format( + BITCOIND_CONFIG['rpcuser'], + BITCOIND_CONFIG['rpcpassword'], + BITCOIND_CONFIG['rpcport']) + self.opts['bitcoin-rpcport'] = self.rpcproxy.rpcport TailableProc.start(self, stdin, stdout_redir=False, stderr_redir=stderr_redir) if wait_for_initialized: From 49c271d0531acebdcd47cab66ac4bad76696c991 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Tue, 7 Jun 2022 08:45:09 -0700 Subject: [PATCH 25/53] Prefix vlsd logging w/ mathcing node ordinal --- contrib/pyln-testing/pyln/testing/utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/contrib/pyln-testing/pyln/testing/utils.py b/contrib/pyln-testing/pyln/testing/utils.py index 778017f5ea5d..4f122098f761 100644 --- a/contrib/pyln-testing/pyln/testing/utils.py +++ b/contrib/pyln-testing/pyln/testing/utils.py @@ -581,7 +581,7 @@ def getnewaddress(self): class ValidatingLightningSignerD(TailableProc): - def __init__(self, vlsd_dir, vlsd_port): + def __init__(self, vlsd_dir, vlsd_port, node_id): TailableProc.__init__(self, vlsd_dir) self.executable = env("REMOTE_SIGNER_CMD", 'vlsd') self.opts = [ @@ -597,7 +597,7 @@ def __init__(self, vlsd_dir, vlsd_port): BITCOIND_CONFIG['rpcpassword'], BITCOIND_CONFIG['rpcport']), ] - self.prefix = 'vlsd' + self.prefix = 'vlsd-%d' % (node_id) self.vlsd_port = vlsd_port @property @@ -638,6 +638,7 @@ def __init__( self.lightning_dir = lightning_dir self.use_vlsd = False self.vlsd_dir = os.path.join(lightning_dir, "vlsd") + self.node_id = node_id self.rpcproxy = bitcoindproxy self.env['CLN_PLUGIN_LOG'] = "cln_plugin=trace,cln_rpc=trace,cln_grpc=trace,debug" @@ -730,7 +731,7 @@ def start(self, stdin=None, wait_for_initialized=True, stderr_redir=False): if self.use_vlsd: # Start the remote signer first vlsd_port = reserve() - self.vlsd = ValidatingLightningSignerD(self.vlsd_dir, vlsd_port) + self.vlsd = ValidatingLightningSignerD(self.vlsd_dir, vlsd_port, self.node_id) self.vlsd.start(stdin, stdout, stderr, wait_for_initialized) # We can't do this in the constructor because we need a new port on each restart. From 645a4511ad2aab7a0bc73bccb82fdff25c816aac Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Mon, 27 Jun 2022 16:46:56 -0700 Subject: [PATCH 26/53] Skip tests which use dev_sign_last_tx to cause breach --- tests/test_closing.py | 2 ++ tests/test_plugin.py | 1 + 2 files changed, 3 insertions(+) diff --git a/tests/test_closing.py b/tests/test_closing.py index 5b30c33c8cf2..f961d1239c82 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -487,6 +487,7 @@ def test_closing_negotiation_step_700sat(node_factory, bitcoind, chainparams): closing_negotiation_step(node_factory, bitcoind, chainparams, opts) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "dev_sign_last_tx causes subsequent validate_holder_commitment_tx failure") @pytest.mark.developer("needs dev-disable-commit-after") def test_penalty_inhtlc(node_factory, bitcoind, executor, chainparams): """Test penalty transaction with an incoming HTLC""" @@ -609,6 +610,7 @@ def test_penalty_inhtlc(node_factory, bitcoind, executor, chainparams): check_utxos_channel(l2, [channel_id], expected_2, tags) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "dev_sign_last_tx causes subsequent validate_holder_commitment_tx failure") @pytest.mark.developer("needs dev-disable-commit-after") def test_penalty_outhtlc(node_factory, bitcoind, executor, chainparams): """Test penalty transaction with an outgoing HTLC""" diff --git a/tests/test_plugin.py b/tests/test_plugin.py index d69f782ba939..12d53b84e35a 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1875,6 +1875,7 @@ def test_replacement_payload(node_factory): assert l2.daemon.wait_for_log("Attept to pay.*with wrong secret") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "dev_sign_last_tx causes subsequent validate_holder_commitment_tx failure") @pytest.mark.developer("Requires dev_sign_last_tx") def test_watchtower(node_factory, bitcoind, directory, chainparams): """Test watchtower hook. From 166142c3be434814d1c979676d6d2b17ff909f3e Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Thu, 14 Jul 2022 16:36:59 -0700 Subject: [PATCH 27/53] Unskip tests which now work with VLS_ENFORCING --- tests/test_misc.py | 1 - tests/test_pay.py | 1 - tests/test_wallet.py | 3 --- 3 files changed, 5 deletions(-) diff --git a/tests/test_misc.py b/tests/test_misc.py index f1fdec3c2c20..5cfea2c6fb2e 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1715,7 +1715,6 @@ def check_new_log(): @unittest.skipIf(VALGRIND, "Valgrind sometimes fails assert on injected SEGV") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "temporary, until fedora-34 gets crashlog libs fixed") def test_crashlog(node_factory): l1 = node_factory.get_node(may_fail=True, allow_broken_log=True) diff --git a/tests/test_pay.py b/tests/test_pay.py index 978f01aeb9f9..9c7c3c526988 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -2706,7 +2706,6 @@ def test_htlc_too_dusty_outgoing(node_factory, bitcoind, chainparams): l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret']) -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "fee above maximum: 81720 > 80000") @pytest.mark.developer("needs DEVELOPER=1 for dev_ignore_htlcs") def test_htlc_too_dusty_incoming(node_factory, bitcoind): """ Try to hit the 'too much dust' limit, should fail the HTLC """ diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 861755ed184d..c96150c38662 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -1025,7 +1025,6 @@ def test_sign_and_send_psbt(node_factory, bitcoind, chainparams): check_coin_moves(l1, 'wallet', wallet_coin_mvts, chainparams) -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't withdraw to non-wallet address") def test_txsend(node_factory, bitcoind, chainparams): amount = 1000000 l1 = node_factory.get_node(random_hsm=True) @@ -1491,7 +1490,6 @@ def test_withdraw_nlocktime_fuzz(node_factory, bitcoind): raise Exception("No transaction with fuzzed nLockTime !") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't withdraw to non-wallet address") def test_multiwithdraw_simple(node_factory, bitcoind, chainparams): """ Test simple multiwithdraw usage. @@ -1593,7 +1591,6 @@ def test_repro_4258(node_factory, bitcoind): @unittest.skipIf(TEST_NETWORK == 'liquid-regtest', "Uses regtest addresses") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't withdraw to non-wallet address") def test_withdraw_bech32m(node_factory, bitcoind): l1 = node_factory.get_node() l1.fundwallet(10000000) From 758d2d4587e456c87cb7f81b43a4b075b479f9a2 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Thu, 14 Jul 2022 16:52:32 -0700 Subject: [PATCH 28/53] Unskip some tests when VLS_PERMISSIVE=1 --- tests/test_closing.py | 6 +++--- tests/test_connection.py | 4 ++-- tests/test_misc.py | 2 +- tests/test_pay.py | 2 +- tests/test_plugin.py | 2 +- tests/test_wallet.py | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test_closing.py b/tests/test_closing.py index f961d1239c82..fb6c706f4296 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -487,7 +487,7 @@ def test_closing_negotiation_step_700sat(node_factory, bitcoind, chainparams): closing_negotiation_step(node_factory, bitcoind, chainparams, opts) -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "dev_sign_last_tx causes subsequent validate_holder_commitment_tx failure") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "dev_sign_last_tx causes subsequent validate_holder_commitment_tx failure") @pytest.mark.developer("needs dev-disable-commit-after") def test_penalty_inhtlc(node_factory, bitcoind, executor, chainparams): """Test penalty transaction with an incoming HTLC""" @@ -610,7 +610,7 @@ def test_penalty_inhtlc(node_factory, bitcoind, executor, chainparams): check_utxos_channel(l2, [channel_id], expected_2, tags) -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "dev_sign_last_tx causes subsequent validate_holder_commitment_tx failure") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "dev_sign_last_tx causes subsequent validate_holder_commitment_tx failure") @pytest.mark.developer("needs dev-disable-commit-after") def test_penalty_outhtlc(node_factory, bitcoind, executor, chainparams): """Test penalty transaction with an outgoing HTLC""" @@ -1577,7 +1577,7 @@ def test_penalty_htlc_tx_timeout(node_factory, bitcoind, chainparams): @pytest.mark.developer("uses dev_sign_last_tx") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "exceeds max fee policy") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "exceeds max fee policy") def test_penalty_rbf_normal(node_factory, bitcoind, executor, chainparams): ''' Test that penalty transactions are RBFed. diff --git a/tests/test_connection.py b/tests/test_connection.py index 557af2f8e1eb..26e95a08858d 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1657,7 +1657,7 @@ def test_funding_v2_cancel_race(node_factory, bitcoind, executor): @pytest.mark.openchannel('v1') @pytest.mark.openchannel('v2') @unittest.skipIf(TEST_NETWORK != 'regtest', "External wallet support doesn't work with elements yet.") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd can't handle random external addresses (allowlist)") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "remote_hsmd can't handle random external addresses (allowlist)") def test_funding_close_upfront(node_factory, bitcoind): opts = {'plugin': os.path.join(os.getcwd(), 'tests/plugins/openchannel_hook_accepter.py')} @@ -2869,7 +2869,7 @@ def mock_donothing(r): @pytest.mark.developer("needs dev_fail") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't withdraw to non-wallet address") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "policy: can't withdraw to non-wallet address") def test_no_fee_estimate(node_factory, bitcoind, executor): l1 = node_factory.get_node(start=False, options={'dev-no-fake-fees': True}) diff --git a/tests/test_misc.py b/tests/test_misc.py index 5cfea2c6fb2e..e2143c4ee6fc 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -470,7 +470,7 @@ def is_p2wpkh(output): assert only_one(fundingtx['vin'])['txid'] == res['wallettxid'] -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't withdraw to non-wallet address") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "policy: can't withdraw to non-wallet address") def test_withdraw_misc(node_factory, bitcoind, chainparams): def dont_spend_outputs(n, txid): """Reserve both outputs (we assume there are two!) in case any our ours, so we don't spend change: wrecks accounting checks""" diff --git a/tests/test_pay.py b/tests/test_pay.py index 9c7c3c526988..c6d75e53724d 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -4313,7 +4313,7 @@ def test_large_mpp_presplit(node_factory): @pytest.mark.developer("builds large network, which is slow if not DEVELOPER") @pytest.mark.slow_test -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't allow push of greater than 20k sat") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "remote_hsmd doesn't allow push of greater than 20k sat") def test_mpp_overload_payee(node_factory, bitcoind): """ We had a bug where if the payer is unusually well-connected compared diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 12d53b84e35a..3dd139a21e1f 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1875,7 +1875,7 @@ def test_replacement_payload(node_factory): assert l2.daemon.wait_for_log("Attept to pay.*with wrong secret") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "dev_sign_last_tx causes subsequent validate_holder_commitment_tx failure") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "dev_sign_last_tx causes subsequent validate_holder_commitment_tx failure") @pytest.mark.developer("Requires dev_sign_last_tx") def test_watchtower(node_factory, bitcoind, directory, chainparams): """Test watchtower hook. diff --git a/tests/test_wallet.py b/tests/test_wallet.py index c96150c38662..3e89393013d8 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -27,7 +27,7 @@ @unittest.skipIf(TEST_NETWORK != 'regtest', "Test relies on a number of example addresses valid only in regtest") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't withdraw to non-wallet address") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "policy: can't withdraw to non-wallet address") def test_withdraw(node_factory, bitcoind): amount = 1000000 # Don't get any funds from previous runs. From c37fb0f909425f79584f45eace5a476e581dcd1a Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Thu, 14 Jul 2022 16:49:15 -0700 Subject: [PATCH 29/53] Comment tests which FAIL with VLS_PERMISSIVE --- tests/test_closing.py | 8 ++++---- tests/test_connection.py | 2 +- tests/test_invoices.py | 2 +- tests/test_opening.py | 2 +- tests/test_wallet.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_closing.py b/tests/test_closing.py index fb6c706f4296..403fc52331db 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -1174,7 +1174,7 @@ def test_channel_lease_lessee_cheat(node_factory, bitcoind, chainparams): @pytest.mark.developer("needs DEVELOPER=1") @unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "Makes use of the sqlite3 db") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't sign revoked commitment number") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't sign revoked commitment number") # FIXME - should work w/ VLS_PERMISSIVE @pytest.mark.slow_test def test_penalty_htlc_tx_fulfill(node_factory, bitcoind, chainparams): """ Test that the penalizing node claims any published @@ -1354,7 +1354,7 @@ def test_penalty_htlc_tx_fulfill(node_factory, bitcoind, chainparams): @pytest.mark.developer("needs DEVELOPER=1") @unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "Makes use of the sqlite3 db") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't sign revoked commitment number") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't sign revoked commitment number") # FIXME - should work with VLS_PERMISSIVE @pytest.mark.slow_test def test_penalty_htlc_tx_timeout(node_factory, bitcoind, chainparams): """ Test that the penalizing node claims any published @@ -3057,7 +3057,7 @@ def test_permfail_htlc_out(node_factory, bitcoind, executor): @pytest.mark.developer("needs DEVELOPER=1") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't withdraw to non-wallet address") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't withdraw to non-wallet address") # FIXME - should work with VLS_PERMISSIVE def test_permfail(node_factory, bitcoind): l1, l2 = node_factory.line_graph(2) @@ -3159,7 +3159,7 @@ def test_shutdown(node_factory): @pytest.mark.developer("needs to set upfront_shutdown_script") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy failure: validate_mutual_close_tx: holder_script doesn't match upfront holder_shutdown_script") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy failure: validate_mutual_close_tx: holder_script doesn't match upfront holder_shutdown_script") # FIXME - should work with VLS_PERMISSIVE def test_option_upfront_shutdown_script(node_factory, bitcoind, executor): l1 = node_factory.get_node(start=False, allow_warning=True) # Insist on upfront script we're not going to match. diff --git a/tests/test_connection.py b/tests/test_connection.py index 26e95a08858d..be754e34edb1 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -3600,7 +3600,7 @@ def test_channel_features(node_factory, bitcoind): @pytest.mark.developer("need dev-force-features") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't support non-option_static_remotekey") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't support non-option_static_remotekey") # FIXME - should work with VLS_PERMISSIVE def test_nonstatic_channel(node_factory, bitcoind): """Smoke test for a channel without option_static_remotekey""" l1, l2 = node_factory.line_graph(2, diff --git a/tests/test_invoices.py b/tests/test_invoices.py index 0c072140ee0f..e671d1ed40c8 100644 --- a/tests/test_invoices.py +++ b/tests/test_invoices.py @@ -130,7 +130,7 @@ def test_invoice_weirdstring(node_factory): l1.rpc.delinvoice(weird_label, "unpaid") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "VLS policies catch two-invoice-one-preimage ahead of the node") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "VLS policies catch two-invoice-one-preimage ahead of the node") # FIXME - should work with VLS_PERMISSIVE def test_invoice_preimage(node_factory): """Test explicit invoice 'preimage'. """ diff --git a/tests/test_opening.py b/tests/test_opening.py index 27506bb8d74d..120ecf74c036 100644 --- a/tests/test_opening.py +++ b/tests/test_opening.py @@ -1203,7 +1203,7 @@ def test_funder_options(node_factory, bitcoind): @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "dual-funding not supported yet") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "dual-funding not supported yet") # FIXME - should work with VLS_PERMISSIVE def test_funder_contribution_limits(node_factory, bitcoind): opts = {'experimental-dual-fund': None, 'feerates': (5000, 5000, 5000, 5000)} diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 3e89393013d8..74a687ec7114 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -816,7 +816,7 @@ def test_psbt_version(node_factory, bitcoind, chainparams): @unittest.skipIf(TEST_NETWORK == 'liquid-regtest', 'Core/Elements need joinpsbt support for v2') -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't withdraw to non-wallet address") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't withdraw to non-wallet address") # FIXME - should work with VLS_PERMISSIVE def test_sign_and_send_psbt(node_factory, bitcoind, chainparams): """ Tests for the sign + send psbt RPCs From 0b1fe4a1ac5205a9037995a0651f7e72f2ec83c7 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Tue, 19 Jul 2022 21:51:59 -0700 Subject: [PATCH 30/53] Skip unless VLS_PERMISSIVE for tests w/ feerates higher than 100_000 --- tests/test_connection.py | 1 + tests/test_pay.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/test_connection.py b/tests/test_connection.py index be754e34edb1..063ee53a2700 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -2461,6 +2461,7 @@ def test_update_fee(node_factory, bitcoind): l2.daemon.wait_for_log('onchaind complete, forgetting peer') +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "feerate above maximum: 110588 > 100000") @pytest.mark.developer def test_fee_limits(node_factory, bitcoind): l1, l2, l3, l4 = node_factory.get_nodes(4, opts=[{'dev-max-fee-multiplier': 5, 'may_reconnect': True, diff --git a/tests/test_pay.py b/tests/test_pay.py index c6d75e53724d..c1c54a169a72 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -2706,6 +2706,7 @@ def test_htlc_too_dusty_outgoing(node_factory, bitcoind, chainparams): l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret']) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "feerate above maximum: 101762 > 100000") @pytest.mark.developer("needs DEVELOPER=1 for dev_ignore_htlcs") def test_htlc_too_dusty_incoming(node_factory, bitcoind): """ Try to hit the 'too much dust' limit, should fail the HTLC """ From 5c7296ac976c36b8d39205a1a9da249c546e2a77 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Wed, 27 Jul 2022 10:53:27 -0700 Subject: [PATCH 31/53] Allow selection of serial demo node with VLS_SERIAL_SELECT --- contrib/pyln-testing/pyln/testing/utils.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/contrib/pyln-testing/pyln/testing/utils.py b/contrib/pyln-testing/pyln/testing/utils.py index 4f122098f761..50ca35f76be8 100644 --- a/contrib/pyln-testing/pyln/testing/utils.py +++ b/contrib/pyln-testing/pyln/testing/utils.py @@ -664,12 +664,20 @@ def __init__( if SUBDAEMON: assert node_id > 0 subdaemons = SUBDAEMON.split(',') - if node_id > len(subdaemons): + # VLS_SERIAL_SELECT "swaps" the selected item with the first item + select = env("VLS_SERIAL_SELECT", '1') + if node_id == int(select): + ndx = 1 + elif node_id == 1: + ndx = int(select) + else: + ndx = node_id + if ndx > len(subdaemons): # use the last element if not as many specifiers as nodes opts['subdaemon'] = subdaemons[-1] else: # use the matching specifier - opts['subdaemon'] = subdaemons[node_id - 1] + opts['subdaemon'] = subdaemons[ndx - 1] print(f"starting node {node_id} with subdaemon {opts['subdaemon']}") if SUBDAEMON == 'hsmd:remote_hsmd': From 2b8ea970933000c48ea97214b340240a73171777 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Fri, 5 Aug 2022 19:07:41 -0700 Subject: [PATCH 32/53] Set network in CLN proxies --- contrib/pyln-testing/pyln/testing/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contrib/pyln-testing/pyln/testing/utils.py b/contrib/pyln-testing/pyln/testing/utils.py index 50ca35f76be8..32b7c6ac8bb6 100644 --- a/contrib/pyln-testing/pyln/testing/utils.py +++ b/contrib/pyln-testing/pyln/testing/utils.py @@ -751,6 +751,9 @@ def start(self, stdin=None, wait_for_initialized=True, stderr_redir=False): BITCOIND_CONFIG['rpcpassword'], BITCOIND_CONFIG['rpcport']) + # The remote hsmd proxies need to know which network we are using + self.env['VLS_NETWORK'] = TEST_NETWORK + self.opts['bitcoin-rpcport'] = self.rpcproxy.rpcport TailableProc.start(self, stdin, stdout_redir=False, stderr_redir=stderr_redir) if wait_for_initialized: From dfdd09871ca5aeeba73286cd3014a40e0b874fc1 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Tue, 9 Aug 2022 13:14:21 -0700 Subject: [PATCH 33/53] Update vlsd test harness wrt reserve_unused_port, stderr_redir, network, and vlsd job control --- contrib/pyln-testing/pyln/testing/utils.py | 37 ++++++++++++++++------ 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/contrib/pyln-testing/pyln/testing/utils.py b/contrib/pyln-testing/pyln/testing/utils.py index 32b7c6ac8bb6..8c41f671ff12 100644 --- a/contrib/pyln-testing/pyln/testing/utils.py +++ b/contrib/pyln-testing/pyln/testing/utils.py @@ -581,13 +581,13 @@ def getnewaddress(self): class ValidatingLightningSignerD(TailableProc): - def __init__(self, vlsd_dir, vlsd_port, node_id): - TailableProc.__init__(self, vlsd_dir) + def __init__(self, vlsd_dir, vlsd_port, node_id, network): + TailableProc.__init__(self, vlsd_dir, verbose=True) self.executable = env("REMOTE_SIGNER_CMD", 'vlsd') self.opts = [ '--log-level-console=DEBUG', '--log-level-disk=DEBUG', - '--network={}'.format(TEST_NETWORK), + '--network={}'.format(network), '--datadir={}'.format(vlsd_dir), '--port={}'.format(vlsd_port), '--initial-allowlist-file={}'.format(env('REMOTE_SIGNER_ALLOWLIST', @@ -604,9 +604,9 @@ def __init__(self, vlsd_dir, vlsd_port, node_id): def cmd_line(self): return [self.executable] + self.opts - def start(self, stdin=None, stdout=None, stderr=None, + def start(self, stdin=None, stdout_redir=True, stderr_redir=True, wait_for_initialized=True): - TailableProc.start(self, stdin, stdout, stderr) + TailableProc.start(self, stdin, stdout_redir, stderr_redir) # We need to always wait for initialization self.wait_for_log("vlsd [0-9]* ready on .*:{}".format(self.vlsd_port)) logging.info("vlsd started") @@ -615,8 +615,11 @@ def stop(self, timeout=10): logging.info("stopping vlsd") rc = TailableProc.stop(self, timeout) logging.info("vlsd stopped") + self.logs_catchup() return rc + def __del__(self): + self.logs_catchup() class LightningD(TailableProc): def __init__( @@ -638,6 +641,8 @@ def __init__( self.lightning_dir = lightning_dir self.use_vlsd = False self.vlsd_dir = os.path.join(lightning_dir, "vlsd") + self.vlsd_port = None + self.vlsd = None self.node_id = node_id self.rpcproxy = bitcoindproxy @@ -734,16 +739,28 @@ def cmd_line(self): return self.cmd_prefix + [self.executable] + self.early_opts + opts + def __del__(self): + if self.vlsd_port is not None: + drop_unused_port(self.vlsd_port) + def start(self, stdin=None, wait_for_initialized=True, stderr_redir=False): try: if self.use_vlsd: + # Kill any previous vlsd (we may have been restarted) + if self.vlsd is not None: + logging.info("killing prior vlsd") + self.vlsd.kill() + # Start the remote signer first - vlsd_port = reserve() - self.vlsd = ValidatingLightningSignerD(self.vlsd_dir, vlsd_port, self.node_id) - self.vlsd.start(stdin, stdout, stderr, wait_for_initialized) + self.vlsd_port = reserve_unused_port() + self.vlsd = ValidatingLightningSignerD( + self.vlsd_dir, self.vlsd_port, self.node_id, self.opts['network']) + self.vlsd.start( + stdin, stdout_redir=True, stderr_redir=True, + wait_for_initialized=wait_for_initialized) # We can't do this in the constructor because we need a new port on each restart. - self.env['REMOTE_HSMD_ENDPOINT'] = '127.0.0.1:{}'.format(vlsd_port) + self.env['REMOTE_HSMD_ENDPOINT'] = '127.0.0.1:{}'.format(self.vlsd_port) # Some of the remote hsmd proxies need a bitcoind RPC connection self.env['BITCOIND_RPC_URL'] = 'http://{}:{}@localhost:{}'.format( @@ -752,7 +769,7 @@ def start(self, stdin=None, wait_for_initialized=True, stderr_redir=False): BITCOIND_CONFIG['rpcport']) # The remote hsmd proxies need to know which network we are using - self.env['VLS_NETWORK'] = TEST_NETWORK + self.env['VLS_NETWORK'] = self.opts['network'] self.opts['bitcoin-rpcport'] = self.rpcproxy.rpcport TailableProc.start(self, stdin, stdout_redir=False, stderr_redir=stderr_redir) From 387315c46ed91a21caa2249024605b75a31f5e54 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Wed, 10 Aug 2022 10:49:21 -0700 Subject: [PATCH 34/53] Skip or require VLS_PERMISSIVE=1 as appropriate --- tests/test_bookkeeper.py | 4 ++++ tests/test_db.py | 1 + tests/test_misc.py | 2 ++ tests/test_opening.py | 1 + tests/test_wallet.py | 1 + 5 files changed, 9 insertions(+) diff --git a/tests/test_bookkeeper.py b/tests/test_bookkeeper.py index 4e7681752112..ceaf78405ff4 100644 --- a/tests/test_bookkeeper.py +++ b/tests/test_bookkeeper.py @@ -120,6 +120,7 @@ def test_bookkeeping_closing_subsat_htlcs(node_factory, bitcoind, chainparams): @unittest.skipIf(TEST_NETWORK != 'regtest', "External wallet support doesn't work with elements yet.") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "remote_hsmd doesn't allow withdrawal to non-wallet, non-allowlisted address") def test_bookkeeping_external_withdraws(node_factory, bitcoind): """ Withdrawals to an external address shouldn't be included in the income statements until confirmed""" @@ -201,6 +202,7 @@ def test_bookkeeping_external_withdraws(node_factory, bitcoind): @unittest.skipIf(TEST_NETWORK != 'regtest', "External wallet support doesn't work with elements yet.") @unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "Depends on sqlite3 database location") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "policy: can't withdraw to non-wallet address") def test_bookkeeping_external_withdraw_missing(node_factory, bitcoind): """ Withdrawals to an external address turn up as extremely large onchain_fees when they happen before @@ -267,6 +269,7 @@ def test_bookkeeping_external_withdraw_missing(node_factory, bitcoind): @unittest.skipIf(TEST_NETWORK != 'regtest', "External wallet support doesn't work with elements yet.") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "policy: can't withdraw to non-wallet address") def test_bookkeeping_rbf_withdraw(node_factory, bitcoind): """ If a withdraw to an external gets RBF'd, it should *not* show up in our income ever. @@ -413,6 +416,7 @@ def _check_events(node, channel_id, exp_events): @unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "turns off bookkeeper at start") @unittest.skipIf(TEST_NETWORK != 'regtest', "network fees hardcoded") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "remote_hsmd doesn't allow push of non-trivial amount") @pytest.mark.openchannel('v1', 'Uses push-msat') def test_bookkeeping_missed_chans_pushed(node_factory, bitcoind): """ diff --git a/tests/test_db.py b/tests/test_db.py index 27252f40408c..b89b86ac21d9 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -446,6 +446,7 @@ def test_sqlite3_builtin_backup(bitcoind, node_factory): @unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "Don't know how to swap dbs in Postgres") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "vlsd chokes on allowlist when started on wrong network") def test_db_sanity_checks(bitcoind, node_factory): l1, l2 = node_factory.get_nodes(2, opts=[{'allow_broken_log': True, 'may_fail': True}, {}]) diff --git a/tests/test_misc.py b/tests/test_misc.py index e2143c4ee6fc..32885efbaac8 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -2594,6 +2594,7 @@ def test_sendcustommsg(node_factory): ]) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd doesn't support dev-force-privkey") @pytest.mark.developer("needs --dev-force-privkey") def test_makesecret(node_factory): """ @@ -2632,6 +2633,7 @@ def test_staticbackup(node_factory): and l1.rpc.staticbackup()["scb"][0][16: 16 + 64] == _["channel_id"]) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd says no such channel") def test_recoverchannel(node_factory): """ Test recoverchannel diff --git a/tests/test_opening.py b/tests/test_opening.py index 120ecf74c036..2a32d36be104 100644 --- a/tests/test_opening.py +++ b/tests/test_opening.py @@ -1410,6 +1410,7 @@ def test_zeroconf_open(bitcoind, node_factory): l2.rpc.pay(inv) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "remote_hsmd inplace and socket don't generate WIRE_HSMD_CUPDATE_SIG_REQ log messages") def test_zeroconf_public(bitcoind, node_factory, chainparams): """Test that we transition correctly from zeroconf to public diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 74a687ec7114..0c0aa7a500d4 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -739,6 +739,7 @@ def test_utxopsbt(node_factory, bitcoind, chainparams): reservedok=True) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "non-beneficial value considered as fees is above maximum feerate") def test_sign_external_psbt(node_factory, bitcoind, chainparams): """ A PSBT w/ one of our inputs should be signable (we can fill From c3a342665b25e5feddd3f966b5fa340d672dea3e Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Wed, 7 Sep 2022 19:36:48 -0700 Subject: [PATCH 35/53] Use more specific failures in validate_onchain_tx w/o dual-funding --- tests/test_connection.py | 1 + tests/test_wallet.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/test_connection.py b/tests/test_connection.py index 063ee53a2700..01c458551084 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1216,6 +1216,7 @@ def test_v2_open(node_factory, bitcoind, chainparams): assert(result['status'] == 'complete') +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "channel push not allowed: dual-funding not supported yet") @pytest.mark.openchannel('v1') def test_funding_push(node_factory, bitcoind, chainparams): """ Try to push peer some sats """ diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 0c0aa7a500d4..0656c34a21de 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -1491,6 +1491,7 @@ def test_withdraw_nlocktime_fuzz(node_factory, bitcoind): raise Exception("No transaction with fuzzed nLockTime !") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "addr2 and addr3 unknown to l1") def test_multiwithdraw_simple(node_factory, bitcoind, chainparams): """ Test simple multiwithdraw usage. From 6859bbdaefd136ce7bb4ea4304cc0dcedaad8bcf Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Wed, 14 Sep 2022 17:26:13 -0700 Subject: [PATCH 36/53] Fix VLS_NETWORK setup bug in cln:native mode --- contrib/pyln-testing/pyln/testing/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/pyln-testing/pyln/testing/utils.py b/contrib/pyln-testing/pyln/testing/utils.py index 8c41f671ff12..cf920caf8d8c 100644 --- a/contrib/pyln-testing/pyln/testing/utils.py +++ b/contrib/pyln-testing/pyln/testing/utils.py @@ -769,7 +769,8 @@ def start(self, stdin=None, wait_for_initialized=True, stderr_redir=False): BITCOIND_CONFIG['rpcport']) # The remote hsmd proxies need to know which network we are using - self.env['VLS_NETWORK'] = self.opts['network'] + if 'network' in self.opts: + self.env['VLS_NETWORK'] = self.opts['network'] self.opts['bitcoin-rpcport'] = self.rpcproxy.rpcport TailableProc.start(self, stdin, stdout_redir=False, stderr_redir=stderr_redir) From f0ff0d82965ee8a1ec62be68f41fd62cc77349ba Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Thu, 15 Sep 2022 17:20:59 -0700 Subject: [PATCH 37/53] Skip BOLT12 tests (invoices derived from offers) Skip test with bolt12 invoice for now --- tests/test_bookkeeper.py | 1 + tests/test_pay.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/tests/test_bookkeeper.py b/tests/test_bookkeeper.py index ceaf78405ff4..60d222c1e3c2 100644 --- a/tests/test_bookkeeper.py +++ b/tests/test_bookkeeper.py @@ -608,6 +608,7 @@ def test_bookkeeping_onchaind_txs(node_factory, bitcoind): assert outs == only_one(wallet_bal['balances'])['balance_msat'] +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "invoice from offer: Invalid bech32: invalid checksum") def test_bookkeeping_descriptions(node_factory, bitcoind, chainparams): """ When an 'invoice' type event comes through, we look up the description details diff --git a/tests/test_pay.py b/tests/test_pay.py index c1c54a169a72..850bb347e13d 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -4550,6 +4550,7 @@ def test_offer(node_factory, bitcoind): assert 'recurrence: every 600 seconds paywindow -10 to +600 (pay proportional)\n' in output +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "Invalid bech32: invalid checksum") def test_offer_deprecated_api(node_factory, bitcoind): l1, l2 = node_factory.line_graph(2, opts={'experimental-offers': None, 'allow-deprecated-apis': True}) @@ -4576,6 +4577,7 @@ def test_fetchinvoice_3hop(node_factory, bitcoind): l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12']}) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "invoice from offer: Invalid bech32: invalid checksum") def test_fetchinvoice(node_factory, bitcoind): # We remove the conversion plugin on l3, causing it to get upset. l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True, @@ -4701,6 +4703,7 @@ def test_fetchinvoice(node_factory, bitcoind): l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12'], 'timeout': 10}) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "Invalid bech32: invalid checksum") def test_fetchinvoice_recurrence(node_factory, bitcoind): """Test for our recurrence extension""" l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True, @@ -4795,6 +4798,7 @@ def test_fetchinvoice_recurrence(node_factory, bitcoind): 'recurrence_label': 'test paywindow'}) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "invoice from offer: Invalid bech32: invalid checksum") @pytest.mark.developer("Needs dev-allow-localhost for autoconnect, dev-force-features to avoid routing onionmsgs") def test_fetchinvoice_autoconnect(node_factory, bitcoind): """We should autoconnect if we need to, to route.""" @@ -4878,6 +4882,7 @@ def test_dev_rawrequest(node_factory): assert 'invoice' in ret +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "sendinvoice: bolt12: Invalid bech32: invalid checksum") def test_sendinvoice(node_factory, bitcoind): l2opts = {'experimental-offers': None} l1, l2 = node_factory.line_graph(2, wait_for_announce=True, From a0cea28c22112a04c28c03db0e8eb89f9ba7b091 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Thu, 15 Sep 2022 17:21:44 -0700 Subject: [PATCH 38/53] Skip invoice test with missing mandatory payment secret --- tests/test_pay.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_pay.py b/tests/test_pay.py index 850bb347e13d..ec90fa0844ae 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -2813,6 +2813,7 @@ def test_tlv_or_legacy(node_factory, bitcoind): l3.daemon.wait_for_log("Got onion.*'type': 'tlv'") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "inv_nosecret: The invoice is missing the mandatory payment secret") @unittest.skipIf(TEST_NETWORK != 'regtest', "Invoice is network specific") def test_pay_no_secret(node_factory, bitcoind): l1, l2 = node_factory.line_graph(2, wait_for_announce=True) From eb81d96b70c96ca514f678ad7dc0520f479f08ce Mon Sep 17 00:00:00 2001 From: Devrandom Date: Tue, 4 Oct 2022 10:24:05 +0200 Subject: [PATCH 39/53] Integrate lssd --- contrib/pyln-testing/pyln/testing/fixtures.py | 24 ++++++++- contrib/pyln-testing/pyln/testing/utils.py | 52 +++++++++++++++++-- tests/fixtures.py | 2 +- tests/test_misc.py | 4 +- 4 files changed, 74 insertions(+), 8 deletions(-) diff --git a/contrib/pyln-testing/pyln/testing/fixtures.py b/contrib/pyln-testing/pyln/testing/fixtures.py index e57852a5fa5a..73445b969fb5 100644 --- a/contrib/pyln-testing/pyln/testing/fixtures.py +++ b/contrib/pyln-testing/pyln/testing/fixtures.py @@ -1,6 +1,6 @@ from concurrent import futures from pyln.testing.db import SqliteDbProvider, PostgresDbProvider -from pyln.testing.utils import NodeFactory, BitcoinD, ElementsD, env, DEVELOPER, LightningNode, TEST_DEBUG +from pyln.testing.utils import NodeFactory, BitcoinD, ElementsD, env, DEVELOPER, LightningNode, TEST_DEBUG, LssD from pyln.client import Millisatoshi from typing import Dict @@ -164,6 +164,25 @@ def bitcoind(directory, teardown_checks): bitcoind.proc.wait() +@pytest.fixture +def lssd(directory, teardown_checks): + lssd = LssD(directory) + + try: + lssd.start() + except Exception: + lssd.stop() + raise + + yield lssd + + try: + lssd.stop() + except Exception: + lssd.proc.kill() + lssd.proc.wait() + + class TeardownErrors(object): def __init__(self): self.errors = [] @@ -446,11 +465,12 @@ def jsonschemas(): @pytest.fixture -def node_factory(request, directory, test_name, bitcoind, executor, db_provider, teardown_checks, node_cls, jsonschemas): +def node_factory(request, directory, test_name, bitcoind, lssd, executor, db_provider, teardown_checks, node_cls, jsonschemas): nf = NodeFactory( request, test_name, bitcoind, + lssd, executor, directory=directory, db_provider=db_provider, diff --git a/contrib/pyln-testing/pyln/testing/utils.py b/contrib/pyln-testing/pyln/testing/utils.py index cf920caf8d8c..ad1c108988f2 100644 --- a/contrib/pyln-testing/pyln/testing/utils.py +++ b/contrib/pyln-testing/pyln/testing/utils.py @@ -385,6 +385,45 @@ def f(*args): return f +class LssD(TailableProc): + def __init__(self, directory, rpcport=None): + lss_dir = os.path.join(directory, 'lss') + TailableProc.__init__(self, lss_dir, verbose=False) + + if rpcport is None: + self.reserved_rpcport = reserve_unused_port() + rpcport = self.reserved_rpcport + else: + self.reserved_rpcport = None + + self.rpcport = rpcport + self.prefix = 'lss' + + if not os.path.exists(lss_dir): + os.makedirs(lss_dir) + + self.cmd_line = [ + 'lssd', + '--datadir={}'.format(lss_dir), + '--port={}'.format(rpcport), + ] + + def __del__(self): + if self.reserved_rpcport is not None: + drop_unused_port(self.reserved_rpcport) + + def start(self): + self.env['RUST_LOG'] = 'debug' + TailableProc.start(self) + self.wait_for_log("ready on", timeout=TIMEOUT) + + logging.info("LssD started") + + def stop(self): + logging.info("Stopping LssD") + return TailableProc.stop(self) + + class BitcoinD(TailableProc): def __init__(self, bitcoin_dir="/tmp/bitcoind-test", rpcport=None): @@ -626,6 +665,7 @@ def __init__( self, lightning_dir, bitcoindproxy, + lssd_port, port=9735, random_hsm=False, node_id=0, @@ -647,6 +687,7 @@ def __init__( self.rpcproxy = bitcoindproxy self.env['CLN_PLUGIN_LOG'] = "cln_plugin=trace,cln_rpc=trace,cln_grpc=trace,debug" + self.lssd_port = lssd_port self.opts = LIGHTNINGD_CONFIG.copy() opts = { @@ -762,6 +803,8 @@ def start(self, stdin=None, wait_for_initialized=True, stderr_redir=False): # We can't do this in the constructor because we need a new port on each restart. self.env['REMOTE_HSMD_ENDPOINT'] = '127.0.0.1:{}'.format(self.vlsd_port) + self.env['VLS_LSS'] = f"http://localhost:{self.lssd_port}" + self.env['RUST_LOG'] = 'debug' # Some of the remote hsmd proxies need a bitcoind RPC connection self.env['BITCOIND_RPC_URL'] = 'http://{}:{}@localhost:{}'.format( BITCOIND_CONFIG['rpcuser'], @@ -860,7 +903,7 @@ def call(self, method, payload=None, cmdprefix=None, filter=None): class LightningNode(object): - def __init__(self, node_id, lightning_dir, bitcoind, executor, valgrind, may_fail=False, + def __init__(self, node_id, lightning_dir, bitcoind, lssd, executor, valgrind, may_fail=False, may_reconnect=False, allow_broken_log=False, allow_warning=False, @@ -870,6 +913,7 @@ def __init__(self, node_id, lightning_dir, bitcoind, executor, valgrind, may_fai valgrind_plugins=True, **kwargs): self.bitcoin = bitcoind + self.lssd = lssd self.executor = executor self.may_fail = may_fail self.may_reconnect = may_reconnect @@ -889,6 +933,7 @@ def __init__(self, node_id, lightning_dir, bitcoind, executor, valgrind, may_fai self.daemon = LightningD( lightning_dir, bitcoindproxy=bitcoind.get_proxy(), + lssd_port=lssd.rpcport, port=port, random_hsm=random_hsm, node_id=node_id, grpc_port=self.grpc_port, ) @@ -1555,7 +1600,7 @@ def flock(directory: Path): class NodeFactory(object): """A factory to setup and start `lightningd` daemons. """ - def __init__(self, request, testname, bitcoind, executor, directory, + def __init__(self, request, testname, bitcoind, lssd, executor, directory, db_provider, node_cls, jsonschemas): if request.node.get_closest_marker("slow_test") and SLOW_MACHINE: self.valgrind = False @@ -1567,6 +1612,7 @@ def __init__(self, request, testname, bitcoind, executor, directory, self.reserved_ports = [] self.executor = executor self.bitcoind = bitcoind + self.lssd = lssd self.directory = directory self.lock = threading.Lock() self.db_provider = db_provider @@ -1652,7 +1698,7 @@ def get_node(self, node_id=None, options=None, dbfile=None, db = self.db_provider.get_db(os.path.join(lightning_dir, TEST_NETWORK), self.testname, node_id) db.provider = self.db_provider node = self.node_cls( - node_id, lightning_dir, self.bitcoind, self.executor, self.valgrind, db=db, + node_id, lightning_dir, self.bitcoind, self.lssd, self.executor, self.valgrind, db=db, port=port, options=options, may_fail=may_fail or expect_fail, jsonschemas=self.jsonschemas, **kwargs diff --git a/tests/fixtures.py b/tests/fixtures.py index 999de007ea09..e8186aed1c13 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -1,5 +1,5 @@ from utils import DEVELOPER, TEST_NETWORK, VALGRIND # noqa: F401,F403 -from pyln.testing.fixtures import directory, test_base_dir, test_name, chainparams, node_factory, bitcoind, teardown_checks, db_provider, executor, setup_logging, jsonschemas # noqa: F401,F403 +from pyln.testing.fixtures import directory, test_base_dir, test_name, chainparams, node_factory, bitcoind, lssd, teardown_checks, db_provider, executor, setup_logging, jsonschemas # noqa: F401,F403 from pyln.testing import utils from utils import COMPAT from pathlib import Path diff --git a/tests/test_misc.py b/tests/test_misc.py index 32885efbaac8..8e1816f42449 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -2459,13 +2459,13 @@ def test_unicode_rpc(node_factory, executor, bitcoind): @unittest.skipIf(VALGRIND, "Testing pyln doesn't exercise anything interesting in the c code.") -def test_unix_socket_path_length(node_factory, bitcoind, directory, executor, db_provider, test_base_dir): +def test_unix_socket_path_length(node_factory, bitcoind, lssd, directory, executor, db_provider, test_base_dir): lightning_dir = os.path.join(directory, "anode" + "far" * 30 + "away") os.makedirs(lightning_dir) db = db_provider.get_db(lightning_dir, "test_unix_socket_path_length", 1) db.provider = db_provider - l1 = LightningNode(1, lightning_dir, bitcoind, executor, VALGRIND, db=db, port=reserve()) + l1 = LightningNode(1, lightning_dir, bitcoind, lssd, executor, VALGRIND, db=db, port=reserve()) # `LightningNode.start()` internally calls `LightningRpc.getinfo()` which # exercises the socket logic, and raises an issue if it fails. From ee5074818b8ab705993f07568d797ed959dcf449 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Mon, 7 Nov 2022 12:51:06 -0800 Subject: [PATCH 40/53] Change vlsd's RPC URL arg to --bitcoin --- contrib/pyln-testing/pyln/testing/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pyln-testing/pyln/testing/utils.py b/contrib/pyln-testing/pyln/testing/utils.py index ad1c108988f2..4b55fa3b0bfb 100644 --- a/contrib/pyln-testing/pyln/testing/utils.py +++ b/contrib/pyln-testing/pyln/testing/utils.py @@ -631,7 +631,7 @@ def __init__(self, vlsd_dir, vlsd_port, node_id, network): '--port={}'.format(vlsd_port), '--initial-allowlist-file={}'.format(env('REMOTE_SIGNER_ALLOWLIST', 'contrib/remote_hsmd/TESTING_ALLOWLIST')), - '--rpc=http://{}:{}@127.0.0.1:{}'.format( + '--bitcoin=http://{}:{}@127.0.0.1:{}'.format( BITCOIND_CONFIG['rpcuser'], BITCOIND_CONFIG['rpcpassword'], BITCOIND_CONFIG['rpcport']), From d2a531f7f21ddaadcacf3b659af44b0c59d7bc2e Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Tue, 14 Feb 2023 13:48:43 -0800 Subject: [PATCH 41/53] Unskip tests after increasing regtest max_feerate_per_kw to 151_000 --- tests/test_connection.py | 1 - tests/test_pay.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_connection.py b/tests/test_connection.py index 01c458551084..0ce627e1f09d 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -2462,7 +2462,6 @@ def test_update_fee(node_factory, bitcoind): l2.daemon.wait_for_log('onchaind complete, forgetting peer') -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "feerate above maximum: 110588 > 100000") @pytest.mark.developer def test_fee_limits(node_factory, bitcoind): l1, l2, l3, l4 = node_factory.get_nodes(4, opts=[{'dev-max-fee-multiplier': 5, 'may_reconnect': True, diff --git a/tests/test_pay.py b/tests/test_pay.py index ec90fa0844ae..09caca7ea8a3 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -2706,7 +2706,7 @@ def test_htlc_too_dusty_outgoing(node_factory, bitcoind, chainparams): l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret']) -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "feerate above maximum: 101762 > 100000") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "feerate above maximum (escalates)") @pytest.mark.developer("needs DEVELOPER=1 for dev_ignore_htlcs") def test_htlc_too_dusty_incoming(node_factory, bitcoind): """ Try to hit the 'too much dust' limit, should fail the HTLC """ From dfe5611cef3ac4095f73860a0271227e667ce2a9 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Tue, 14 Feb 2023 14:53:35 -0800 Subject: [PATCH 42/53] ci: add curl to setup.sh --- .github/scripts/setup.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/scripts/setup.sh b/.github/scripts/setup.sh index 4a7ebd5bc4b0..936eac518529 100755 --- a/.github/scripts/setup.sh +++ b/.github/scripts/setup.sh @@ -15,6 +15,7 @@ sudo apt-get -qq install --no-install-recommends --allow-unauthenticated -yy \ build-essential \ clang \ cppcheck \ + curl \ docbook-xml \ eatmydata \ gcc-aarch64-linux-gnu \ @@ -82,4 +83,4 @@ sudo chmod a+x /usr/local/bin/protoc export PROTOC=/usr/local/bin/protoc export PATH=$PATH:/usr/local/bin env -ls -lha /usr/local/bin \ No newline at end of file +ls -lha /usr/local/bin From 6ed5720b0e25790dea92c99c07b9d9e9526194d7 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Tue, 14 Feb 2023 15:57:24 -0800 Subject: [PATCH 43/53] ci: remove remote_hsmd from Makefile --- Makefile | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 520cc0e8a30f..a5da85b84d87 100644 --- a/Makefile +++ b/Makefile @@ -242,8 +242,7 @@ LIBRARY_PATH := /usr/local/lib endif CPPFLAGS += -DBINTOPKGLIBEXECDIR="\"$(shell sh tools/rel.sh $(bindir) $(pkglibexecdir))\"" -CMNFLAGS = $(CDEBUGFLAGS) $(COPTFLAGS) -I $(CCANDIR) $(EXTERNAL_INCLUDE_FLAGS) -I . -I$(CPATH) $(SQLITE3_CFLAGS) $(POSTGRES_INCLUDE) $(FEATURES) $(COVFLAGS) $(DEV_CFLAGS) -DSHACHAIN_BITS=48 -DJSMN_PARENT_LINKS $(PIE_CFLAGS) $(COMPAT_CFLAGS) $(CSANFLAGS) -DBUILD_ELEMENTS=1 -CFLAGS = $(CPPFLAGS) $(CWARNFLAGS) $(CMNFLAGS) +CFLAGS = $(CPPFLAGS) $(CWARNFLAGS) $(CDEBUGFLAGS) $(COPTFLAGS) -I $(CCANDIR) $(EXTERNAL_INCLUDE_FLAGS) -I . -I$(CPATH) $(SQLITE3_CFLAGS) $(POSTGRES_INCLUDE) $(FEATURES) $(COVFLAGS) $(DEV_CFLAGS) -DSHACHAIN_BITS=48 -DJSMN_PARENT_LINKS $(PIE_CFLAGS) $(COMPAT_CFLAGS) $(CSANFLAGS) -DBUILD_ELEMENTS=1 # If CFLAGS is already set in the environment of make (to whatever value, it # does not matter) then it would export it to subprocesses with the above value @@ -360,7 +359,6 @@ include devtools/Makefile include tools/Makefile include plugins/Makefile include tests/plugins/Makefile -include contrib/remote_hsmd/Makefile ifneq ($(FUZZING),0) include tests/fuzz/Makefile @@ -409,8 +407,7 @@ PKGLIBEXEC_PROGRAMS = \ lightningd/lightning_hsmd \ lightningd/lightning_onchaind \ lightningd/lightning_openingd \ - lightningd/lightning_websocketd \ - lightningd/remote_hsmd + lightningd/lightning_websocketd mkdocs.yml: $(MANPAGES:=.md) @$(call VERBOSE, "genidx $@", \ @@ -446,10 +443,6 @@ else PYTEST_OPTS += -x endif -ifneq ($(PYTEST_MOREOPTS),) -PYTEST_OPTS += $(PYTEST_MOREOPTS) -endif - check-units: check: check-units installcheck check-protos pytest From 5ca69f209c2789cbcb4d02842b9420f11b15e740 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Wed, 15 Feb 2023 16:41:54 -0800 Subject: [PATCH 44/53] Skip dual funding tests which trigger policy failure - "channel stub can only return point for commitment number zero" - likely caused by CLN commit c0cc285a - possibly we relax the check, may not be security critical - See issue https://gitlab.com/lightning-signer/validating-lightning-signer/-/issues/245 --- tests/test_connection.py | 1 + tests/test_opening.py | 1 + tests/test_plugin.py | 1 + 3 files changed, 3 insertions(+) diff --git a/tests/test_connection.py b/tests/test_connection.py index 0ce627e1f09d..bbd63d501cd5 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1806,6 +1806,7 @@ def test_funding_external_wallet(node_factory, bitcoind): @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') @pytest.mark.openchannel('v1') # We manually turn on dual-funding for select nodes +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "commit c0cc285a causes: channel stub can only return point for commitment number zero") def test_multifunding_v1_v2_mixed(node_factory, bitcoind): ''' Simple test for multifundchannel, using v1 + v2 diff --git a/tests/test_opening.py b/tests/test_opening.py index 2a32d36be104..c24ca160f9e7 100644 --- a/tests/test_opening.py +++ b/tests/test_opening.py @@ -63,6 +63,7 @@ def test_queryrates(node_factory, bitcoind): @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') @pytest.mark.developer("uses dev-disconnect") @pytest.mark.openchannel('v1') # Mixed v1 + v2, v2 manually turned on +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "commit c0cc285a causes: channel stub can only return point for commitment number zero") def test_multifunding_v2_best_effort(node_factory, bitcoind): ''' Check that best_effort flag works. diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 3dd139a21e1f..b8868f9c1b7e 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -3405,6 +3405,7 @@ def test_block_added_notifications(node_factory, bitcoind): @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') @pytest.mark.developer("wants dev-announce-localhost so we see listnodes.addresses") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "commit c0cc285a causes: channel stub can only return point for commitment number zero") def test_sql(node_factory, bitcoind): opts = {'experimental-offers': None, 'experimental-dual-fund': None, From 4ef913c52c2b84dcad5a5c34ec0f0819312b6e6d Mon Sep 17 00:00:00 2001 From: Devrandom Date: Tue, 21 Feb 2023 15:35:29 +0100 Subject: [PATCH 45/53] replace vlsd support with vlsd2 --- contrib/pyln-testing/pyln/testing/utils.py | 71 +++++++++++----------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/contrib/pyln-testing/pyln/testing/utils.py b/contrib/pyln-testing/pyln/testing/utils.py index 4b55fa3b0bfb..57a9e0b995b1 100644 --- a/contrib/pyln-testing/pyln/testing/utils.py +++ b/contrib/pyln-testing/pyln/testing/utils.py @@ -622,21 +622,17 @@ def getnewaddress(self): class ValidatingLightningSignerD(TailableProc): def __init__(self, vlsd_dir, vlsd_port, node_id, network): TailableProc.__init__(self, vlsd_dir, verbose=True) - self.executable = env("REMOTE_SIGNER_CMD", 'vlsd') + self.executable = env("REMOTE_SIGNER_CMD", 'vlsd2') + os.environ['ALLOWLIST'] = env( + 'REMOTE_SIGNER_ALLOWLIST', + 'contrib/remote_hsmd/TESTING_ALLOWLIST') self.opts = [ - '--log-level-console=DEBUG', - '--log-level-disk=DEBUG', '--network={}'.format(network), '--datadir={}'.format(vlsd_dir), - '--port={}'.format(vlsd_port), - '--initial-allowlist-file={}'.format(env('REMOTE_SIGNER_ALLOWLIST', - 'contrib/remote_hsmd/TESTING_ALLOWLIST')), - '--bitcoin=http://{}:{}@127.0.0.1:{}'.format( - BITCOIND_CONFIG['rpcuser'], - BITCOIND_CONFIG['rpcpassword'], - BITCOIND_CONFIG['rpcport']), - ] - self.prefix = 'vlsd-%d' % (node_id) + '--connect=http://localhost:{}'.format(vlsd_port), + '--integration-test', + ] + self.prefix = 'vlsd2-%d' % (node_id) self.vlsd_port = vlsd_port @property @@ -647,13 +643,13 @@ def start(self, stdin=None, stdout_redir=True, stderr_redir=True, wait_for_initialized=True): TailableProc.start(self, stdin, stdout_redir, stderr_redir) # We need to always wait for initialization - self.wait_for_log("vlsd [0-9]* ready on .*:{}".format(self.vlsd_port)) - logging.info("vlsd started") + self.wait_for_log("vlsd2 git_desc") + logging.info("vlsd2 started") def stop(self, timeout=10): - logging.info("stopping vlsd") + logging.info("stopping vlsd2") rc = TailableProc.stop(self, timeout) - logging.info("vlsd stopped") + logging.info("vlsd2 stopped") self.logs_catchup() return rc @@ -726,7 +722,7 @@ def __init__( opts['subdaemon'] = subdaemons[ndx - 1] print(f"starting node {node_id} with subdaemon {opts['subdaemon']}") - if SUBDAEMON == 'hsmd:remote_hsmd': + if SUBDAEMON == 'hsmd:remote_hsmd_socket': self.use_vlsd = True for k, v in opts.items(): @@ -756,9 +752,8 @@ def __init__( def cleanup(self): if self.use_vlsd: - if self.use_vlsd: - # Make sure the remotesigner is shutdown - self.vlsd.stop() + # Make sure the remotesigner is shutdown + self.vlsd.stop() # To force blackhole to exit, disconnect file must be truncated! if self.disconnect_file: @@ -786,23 +781,6 @@ def __del__(self): def start(self, stdin=None, wait_for_initialized=True, stderr_redir=False): try: - if self.use_vlsd: - # Kill any previous vlsd (we may have been restarted) - if self.vlsd is not None: - logging.info("killing prior vlsd") - self.vlsd.kill() - - # Start the remote signer first - self.vlsd_port = reserve_unused_port() - self.vlsd = ValidatingLightningSignerD( - self.vlsd_dir, self.vlsd_port, self.node_id, self.opts['network']) - self.vlsd.start( - stdin, stdout_redir=True, stderr_redir=True, - wait_for_initialized=wait_for_initialized) - - # We can't do this in the constructor because we need a new port on each restart. - self.env['REMOTE_HSMD_ENDPOINT'] = '127.0.0.1:{}'.format(self.vlsd_port) - self.env['VLS_LSS'] = f"http://localhost:{self.lssd_port}" self.env['RUST_LOG'] = 'debug' # Some of the remote hsmd proxies need a bitcoind RPC connection @@ -816,7 +794,26 @@ def start(self, stdin=None, wait_for_initialized=True, stderr_redir=False): self.env['VLS_NETWORK'] = self.opts['network'] self.opts['bitcoin-rpcport'] = self.rpcproxy.rpcport + + if self.use_vlsd: + self.vlsd_port = reserve_unused_port() + # We can't do this in the constructor because we need a new port on each restart. + self.env['VLS_PORT'] = str(self.vlsd_port) + # Kill any previous vlsd (we may have been restarted) + if self.vlsd is not None: + logging.info("killing prior vlsd") + self.vlsd.kill() + TailableProc.start(self, stdin, stdout_redir=False, stderr_redir=stderr_redir) + + if self.use_vlsd: + # Start the remote signer first + self.vlsd = ValidatingLightningSignerD( + self.vlsd_dir, self.vlsd_port, self.node_id, self.opts['network']) + self.vlsd.start( + stdin, stdout_redir=True, stderr_redir=True, + wait_for_initialized=wait_for_initialized) + if wait_for_initialized: self.wait_for_log("Server started with public key") logging.info("LightningD started") From b257fefb5de0b3aa7741f6e7291f6edcf86271a1 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Thu, 27 Apr 2023 19:17:21 -0700 Subject: [PATCH 46/53] Unskip (completely) tests w/ unknown outputs because auto_approve --- tests/test_bookkeeper.py | 3 --- tests/test_closing.py | 3 +-- tests/test_connection.py | 3 +-- tests/test_misc.py | 1 - tests/test_wallet.py | 2 -- 5 files changed, 2 insertions(+), 10 deletions(-) diff --git a/tests/test_bookkeeper.py b/tests/test_bookkeeper.py index 60d222c1e3c2..6d0435e97c40 100644 --- a/tests/test_bookkeeper.py +++ b/tests/test_bookkeeper.py @@ -120,7 +120,6 @@ def test_bookkeeping_closing_subsat_htlcs(node_factory, bitcoind, chainparams): @unittest.skipIf(TEST_NETWORK != 'regtest', "External wallet support doesn't work with elements yet.") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "remote_hsmd doesn't allow withdrawal to non-wallet, non-allowlisted address") def test_bookkeeping_external_withdraws(node_factory, bitcoind): """ Withdrawals to an external address shouldn't be included in the income statements until confirmed""" @@ -202,7 +201,6 @@ def test_bookkeeping_external_withdraws(node_factory, bitcoind): @unittest.skipIf(TEST_NETWORK != 'regtest', "External wallet support doesn't work with elements yet.") @unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "Depends on sqlite3 database location") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "policy: can't withdraw to non-wallet address") def test_bookkeeping_external_withdraw_missing(node_factory, bitcoind): """ Withdrawals to an external address turn up as extremely large onchain_fees when they happen before @@ -269,7 +267,6 @@ def test_bookkeeping_external_withdraw_missing(node_factory, bitcoind): @unittest.skipIf(TEST_NETWORK != 'regtest', "External wallet support doesn't work with elements yet.") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "policy: can't withdraw to non-wallet address") def test_bookkeeping_rbf_withdraw(node_factory, bitcoind): """ If a withdraw to an external gets RBF'd, it should *not* show up in our income ever. diff --git a/tests/test_closing.py b/tests/test_closing.py index 403fc52331db..44644014e35f 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -3055,9 +3055,8 @@ def test_permfail_htlc_out(node_factory, bitcoind, executor): bitcoind.generate_block(2) wait_for(lambda: l2.rpc.listpeers()['peers'] == []) - @pytest.mark.developer("needs DEVELOPER=1") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't withdraw to non-wallet address") # FIXME - should work with VLS_PERMISSIVE +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "policy: can't withdraw to non-wallet address") # FIXME - should work with auto-approve def test_permfail(node_factory, bitcoind): l1, l2 = node_factory.line_graph(2) diff --git a/tests/test_connection.py b/tests/test_connection.py index bbd63d501cd5..45eff96dfa72 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1658,7 +1658,7 @@ def test_funding_v2_cancel_race(node_factory, bitcoind, executor): @pytest.mark.openchannel('v1') @pytest.mark.openchannel('v2') @unittest.skipIf(TEST_NETWORK != 'regtest', "External wallet support doesn't work with elements yet.") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "remote_hsmd can't handle random external addresses (allowlist)") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "remote_hsmd can't handle random external addresses (allowlist)") # FIXME - should work w/ auto-approve def test_funding_close_upfront(node_factory, bitcoind): opts = {'plugin': os.path.join(os.getcwd(), 'tests/plugins/openchannel_hook_accepter.py')} @@ -2871,7 +2871,6 @@ def mock_donothing(r): @pytest.mark.developer("needs dev_fail") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "policy: can't withdraw to non-wallet address") def test_no_fee_estimate(node_factory, bitcoind, executor): l1 = node_factory.get_node(start=False, options={'dev-no-fake-fees': True}) diff --git a/tests/test_misc.py b/tests/test_misc.py index 8e1816f42449..30842130cc63 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -470,7 +470,6 @@ def is_p2wpkh(output): assert only_one(fundingtx['vin'])['txid'] == res['wallettxid'] -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "policy: can't withdraw to non-wallet address") def test_withdraw_misc(node_factory, bitcoind, chainparams): def dont_spend_outputs(n, txid): """Reserve both outputs (we assume there are two!) in case any our ours, so we don't spend change: wrecks accounting checks""" diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 0656c34a21de..5703b8fb8341 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -27,7 +27,6 @@ @unittest.skipIf(TEST_NETWORK != 'regtest', "Test relies on a number of example addresses valid only in regtest") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "policy: can't withdraw to non-wallet address") def test_withdraw(node_factory, bitcoind): amount = 1000000 # Don't get any funds from previous runs. @@ -1491,7 +1490,6 @@ def test_withdraw_nlocktime_fuzz(node_factory, bitcoind): raise Exception("No transaction with fuzzed nLockTime !") -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "addr2 and addr3 unknown to l1") def test_multiwithdraw_simple(node_factory, bitcoind, chainparams): """ Test simple multiwithdraw usage. From 59a4ac385a76105cbc3495435a5ab4a5d2d11341 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Tue, 9 May 2023 14:57:57 -0700 Subject: [PATCH 47/53] gitignore VLS proxy daemons --- lightningd/.gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lightningd/.gitignore b/lightningd/.gitignore index b2f53af9886f..3b7704593aa8 100644 --- a/lightningd/.gitignore +++ b/lightningd/.gitignore @@ -10,3 +10,6 @@ lightning_openingd lightning_websocketd lightningd remote_hsmd +remote_hsmd_inplace +remote_hsmd_serial +remote_hsmd_socket From 963ac5226a91b326822d17d1cb8e6904e694c695 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Fri, 19 May 2023 20:31:15 -0700 Subject: [PATCH 48/53] Skip zeroconf tests unless PERMISSIVE --- tests/test_opening.py | 3 +++ tests/test_pay.py | 1 + 2 files changed, 4 insertions(+) diff --git a/tests/test_opening.py b/tests/test_opening.py index c24ca160f9e7..967a793b13bb 100644 --- a/tests/test_opening.py +++ b/tests/test_opening.py @@ -1347,6 +1347,7 @@ def test_zeroconf_mindepth(bitcoind, node_factory): wait_for(lambda: only_one(l2.rpc.listpeerchannels()['channels'])['state'] == "CHANNELD_NORMAL") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "tried commitment when funding is not buried ") def test_zeroconf_open(bitcoind, node_factory): """Let's open a zeroconf channel @@ -1515,6 +1516,7 @@ def test_zeroconf_public(bitcoind, node_factory, chainparams): wait_for(lambda: only_one([x for x in n.rpc.bkpr_listbalances()['accounts'] if x['account'] == channel_id])['account_resolved']) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "tried commitment when funding is not buried ") def test_zeroconf_forward(node_factory, bitcoind): """Ensure that we can use zeroconf channels in forwards. @@ -1769,6 +1771,7 @@ def test_scid_alias_private(node_factory, bitcoind): l1.rpc.waitsendpay(inv['payment_hash']) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "tried commitment when funding is not buried ") def test_zeroconf_multichan_forward(node_factory): """The freedom to choose the forward channel bytes us when it is 0conf diff --git a/tests/test_pay.py b/tests/test_pay.py index 09caca7ea8a3..f51ae0fb9efe 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -5324,6 +5324,7 @@ def test_payerkey(node_factory): assert n.rpc.decode(b12)['invreq_payer_id'] == k +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "tried commitment when funding is not buried ") def test_pay_multichannel_use_zeroconf(bitcoind, node_factory): """Check that we use the zeroconf direct channel to pay when we need to""" # 0. Setup normal channel, 200k sats. From a61ed5e22cb32cf32950790901cda7650bd2fe4a Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Wed, 7 Jun 2023 14:16:26 -0700 Subject: [PATCH 49/53] tests: Add call to preapproveinvoice in pay to inform signer --- contrib/pyln-testing/pyln/testing/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contrib/pyln-testing/pyln/testing/utils.py b/contrib/pyln-testing/pyln/testing/utils.py index 57a9e0b995b1..6a10a5f90971 100644 --- a/contrib/pyln-testing/pyln/testing/utils.py +++ b/contrib/pyln-testing/pyln/testing/utils.py @@ -1355,6 +1355,9 @@ def pay(self, dst, amt, label=None): 'channel': scid } + # let the signer know this payment is coming + self.rpc.preapproveinvoice(bolt11=inv['bolt11']) + # sendpay is async now self.rpc.sendpay([routestep], rhash, payment_secret=psecret, bolt11=inv['bolt11']) # wait for sendpay to comply From 017d50bc305f4f5a48c51770515fa9d26112f1d2 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Thu, 8 Jun 2023 09:31:06 -0700 Subject: [PATCH 50/53] tests: Add explicit preapprove{invoice,keysend} calls before sendpay --- tests/test_closing.py | 8 ++++++++ tests/test_connection.py | 7 +++++++ tests/test_pay.py | 24 ++++++++++++++++++++++++ tests/test_plugin.py | 12 ++++++++++++ 4 files changed, 51 insertions(+) diff --git a/tests/test_closing.py b/tests/test_closing.py index 44644014e35f..30a65fd6b86b 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -1824,6 +1824,7 @@ def test_onchaind_replay(node_factory, bitcoind): 'delay': 101, 'channel': first_scid(l1, l2) } + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay([routestep], rhash, payment_secret=inv['payment_secret']) l1.daemon.wait_for_log('sendrawtx exit 0') bitcoind.generate_block(1, wait_for_mempool=1) @@ -1961,6 +1962,7 @@ def test_onchain_timeout(node_factory, bitcoind, executor): 'channel': first_scid(l1, l2) } + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay([routestep], rhash, payment_secret=inv['payment_secret'], groupid=1) with pytest.raises(RpcError): l1.rpc.waitsendpay(rhash) @@ -2086,6 +2088,7 @@ def test_onchain_middleman_simple(node_factory, bitcoind): q = queue.Queue() + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming def try_pay(): try: l1.rpc.sendpay(route, rhash, payment_secret=inv['payment_secret']) @@ -2214,6 +2217,7 @@ def test_onchain_middleman_their_unilateral_in(node_factory, bitcoind): q = queue.Queue() + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming def try_pay(): try: l1.rpc.sendpay(route, rhash, payment_secret=inv['payment_secret']) @@ -2318,6 +2322,8 @@ def try_pay(): try: # rhash is fake (so is payment_secret) rhash = 'B1' * 32 + # let the signer know this payment is coming + l1.rpc.preapprovekeysend(l2.info['id'], rhash, 10**8) l1.rpc.sendpay(route, rhash, payment_secret=rhash) q.put(None) except Exception as err: @@ -2457,6 +2463,7 @@ def test_onchain_feechange(node_factory, bitcoind, executor): 'channel': first_scid(l1, l2) } + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming executor.submit(l1.rpc.sendpay, [routestep], rhash, payment_secret=inv['payment_secret']) # l2 will drop to chain. @@ -2538,6 +2545,7 @@ def test_onchain_all_dust(node_factory, bitcoind, executor): 'channel': first_scid(l1, l2) } + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming executor.submit(l1.rpc.sendpay, [routestep], rhash, payment_secret=inv['payment_secret']) # l2 will drop to chain. diff --git a/tests/test_connection.py b/tests/test_connection.py index 45eff96dfa72..ac5a9eac191d 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -849,6 +849,7 @@ def test_reconnect_sender_add1(node_factory): l1.daemon.wait_for_log('Already have funding locked in') # This will send commit, so will reconnect as required. + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(route, rhash, payment_secret=inv['payment_secret']) @@ -880,6 +881,7 @@ def test_reconnect_sender_add(node_factory): route = [{'amount_msat': amt, 'id': l2.info['id'], 'delay': 5, 'channel': first_scid(l1, l2)}] # This will send commit, so will reconnect as required. + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(route, rhash, payment_secret=inv['payment_secret']) # Should have printed this for every reconnect. for i in range(0, len(disconnects)): @@ -912,6 +914,7 @@ def test_reconnect_receiver_add(node_factory): assert only_one(l2.rpc.listinvoices('testpayment2')['invoices'])['status'] == 'unpaid' route = [{'amount_msat': amt, 'id': l2.info['id'], 'delay': 5, 'channel': first_scid(l1, l2)}] + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(route, rhash, payment_secret=inv['payment_secret']) for i in range(len(disconnects)): l1.daemon.wait_for_log('Already have funding locked in') @@ -942,6 +945,7 @@ def test_reconnect_receiver_fulfill(node_factory): assert only_one(l2.rpc.listinvoices('testpayment2')['invoices'])['status'] == 'unpaid' route = [{'amount_msat': amt, 'id': l2.info['id'], 'delay': 5, 'channel': first_scid(l1, l2)}] + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(route, rhash, payment_secret=inv['payment_secret']) for i in range(len(disconnects)): l1.daemon.wait_for_log('Already have funding locked in') @@ -4197,6 +4201,7 @@ def test_multichan(node_factory, executor, bitcoind): before = l2.rpc.listpeerchannels(l3.info['id'])['channels'] inv1 = l3.rpc.invoice(100000000, "invoice", "invoice") + l1.rpc.preapproveinvoice(bolt11=inv1['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(route, inv1['payment_hash'], payment_secret=inv1['payment_secret']) l1.rpc.waitsendpay(inv1['payment_hash']) @@ -4224,6 +4229,7 @@ def test_multichan(node_factory, executor, bitcoind): before = l2.rpc.listpeerchannels(l3.info['id'])['channels'] route[1]['channel'] = scid23b inv2 = l3.rpc.invoice(100000000, "invoice2", "invoice2") + l1.rpc.preapproveinvoice(bolt11=inv2['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(route, inv2['payment_hash'], payment_secret=inv2['payment_secret']) l1.rpc.waitsendpay(inv2['payment_hash']) # Wait until HTLCs fully settled @@ -4268,6 +4274,7 @@ def test_multichan(node_factory, executor, bitcoind): # We can actually pay by *closed* scid (at least until it's completely forgotten) route[1]['channel'] = scid23a inv3 = l3.rpc.invoice(100000000, "invoice3", "invoice3") + l1.rpc.preapproveinvoice(bolt11=inv3['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(route, inv3['payment_hash'], payment_secret=inv3['payment_secret']) l1.rpc.waitsendpay(inv3['payment_hash']) diff --git a/tests/test_pay.py b/tests/test_pay.py index f51ae0fb9efe..d82a14beabaa 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -581,6 +581,7 @@ def invoice_unpaid(dst, label): with pytest.raises(RpcError): rs = copy.deepcopy(routestep) rs['amount_msat'] = rs['amount_msat'] - 1 + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay([rs], rhash, payment_secret=inv['payment_secret']) l1.rpc.waitsendpay(rhash) assert invoice_unpaid(l2, 'testpayment2') @@ -589,6 +590,7 @@ def invoice_unpaid(dst, label): with pytest.raises(RpcError): rs = copy.deepcopy(routestep) rs['amount_msat'] = rs['amount_msat'] * 2 + 1 + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay([rs], rhash, payment_secret=inv['payment_secret']) l1.rpc.waitsendpay(rhash) assert invoice_unpaid(l2, 'testpayment2') @@ -597,6 +599,7 @@ def invoice_unpaid(dst, label): with pytest.raises(RpcError): rs = copy.deepcopy(routestep) rs['delay'] = rs['delay'] - 2 + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay([rs], rhash, payment_secret=inv['payment_secret']) l1.rpc.waitsendpay(rhash) assert invoice_unpaid(l2, 'testpayment2') @@ -606,17 +609,20 @@ def invoice_unpaid(dst, label): with pytest.raises(RpcError): rs = copy.deepcopy(routestep) rs['id'] = '00000000000000000000000000000000' + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay([rs], rhash, payment_secret=inv['payment_secret']) assert invoice_unpaid(l2, 'testpayment2') l1.rpc.check_request_schemas = True # Bad payment_secret + l1.rpc.preapprovekeysend(l2.info['id'], rhash, routestep['amount_msat']) l1.rpc.sendpay([routestep], rhash, payment_secret="00" * 32) with pytest.raises(RpcError): l1.rpc.waitsendpay(rhash) assert invoice_unpaid(l2, 'testpayment2') # Missing payment_secret + l1.rpc.preapprovekeysend(l2.info['id'], rhash, routestep['amount_msat']) l1.rpc.sendpay([routestep], rhash) with pytest.raises(RpcError): l1.rpc.waitsendpay(rhash) @@ -632,6 +638,7 @@ def invoice_unpaid(dst, label): # This works. before = int(time.time()) + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming details = l1.rpc.sendpay([routestep], rhash, payment_secret=inv['payment_secret']) after = int(time.time()) preimage = l1.rpc.waitsendpay(rhash)['payment_preimage'] @@ -674,6 +681,7 @@ def check_balances(): rhash = inv['payment_hash'] assert only_one(l2.rpc.listinvoices('testpayment3')['invoices'])['status'] == 'unpaid' routestep = {'amount_msat': amt * 2, 'id': l2.info['id'], 'delay': 5, 'channel': first_scid(l1, l2)} + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay([routestep], rhash, payment_secret=inv['payment_secret']) preimage3 = l1.rpc.waitsendpay(rhash)['payment_preimage'] assert only_one(l2.rpc.listinvoices('testpayment3')['invoices'])['status'] == 'paid' @@ -1105,6 +1113,7 @@ def test_forward(node_factory, bitcoind): # Unknown other peer route = copy.deepcopy(baseroute) route[1]['id'] = '031a8dc444e41bb989653a4501e11175a488a57439b0c4947704fd6e3de5dca607' + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(route, rhash, payment_secret=inv['payment_secret']) with pytest.raises(RpcError): l1.rpc.waitsendpay(rhash) @@ -1511,6 +1520,7 @@ def test_forward_local_failed_stats(node_factory, bitcoind, executor): l2.rpc.close(c23, 1) + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming with pytest.raises(RpcError): l1.rpc.sendpay(route, payment_hash, payment_secret=inv['payment_secret']) l1.rpc.waitsendpay(payment_hash) @@ -1536,6 +1546,7 @@ def test_forward_local_failed_stats(node_factory, bitcoind, executor): 'delay': 6, 'channel': c24}] + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming with pytest.raises(RpcError): l1.rpc.sendpay(route, payment_hash, payment_secret=inv['payment_secret']) l1.rpc.waitsendpay(payment_hash) @@ -1562,6 +1573,7 @@ def test_forward_local_failed_stats(node_factory, bitcoind, executor): 'delay': 6, 'channel': c25}] + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming with pytest.raises(RpcError): l1.rpc.sendpay(route, payment_hash, payment_secret=inv['payment_secret']) l1.rpc.waitsendpay(payment_hash) @@ -1585,6 +1597,7 @@ def test_forward_local_failed_stats(node_factory, bitcoind, executor): # Replace id with a different pubkey, so onion encoded badly at l2 hop. route[1]['id'] = mangled_nodeid + l6.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming with pytest.raises(RpcError): l6.rpc.sendpay(route, payment_hash, payment_secret=inv['payment_secret']) l6.rpc.waitsendpay(payment_hash) @@ -1612,6 +1625,7 @@ def test_forward_local_failed_stats(node_factory, bitcoind, executor): 'delay': 5, 'channel': c24}] + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming executor.submit(l1.rpc.sendpay, route, payment_hash, payment_secret=inv['payment_secret']) l4.daemon.wait_for_log('permfail') @@ -1668,6 +1682,7 @@ def test_htlcs_cltv_only_difference(node_factory, bitcoind): # L2 tries to pay r = l2.rpc.getroute(l4.info['id'], 10**8, 1)["route"] + l2.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l2.rpc.sendpay(r, h, payment_secret=inv['payment_secret']) # Now increment CLTV @@ -1676,6 +1691,7 @@ def test_htlcs_cltv_only_difference(node_factory, bitcoind): # L1 tries to pay r = l1.rpc.getroute(l4.info['id'], 10**8, 1)["route"] + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(r, h, payment_secret=inv['payment_secret']) # Now increment CLTV @@ -1684,6 +1700,7 @@ def test_htlcs_cltv_only_difference(node_factory, bitcoind): # L3 tries to pay r = l3.rpc.getroute(l4.info['id'], 10**8, 1)["route"] + l3.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l3.rpc.sendpay(r, h, payment_secret=inv['payment_secret']) # Give them time to go through. @@ -1750,6 +1767,7 @@ def exhaust_channel(opener, peer, scid, already_spent=0): 'delay': 10, 'channel': scid } + opener.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming opener.rpc.sendpay([routestep], inv['payment_hash'], payment_secret=inv['payment_secret']) opener.rpc.waitsendpay(inv['payment_hash']) @@ -2476,6 +2494,7 @@ def test_channel_spendable(node_factory, bitcoind): # Exact amount should succeed. route = l1.rpc.getroute(l2.info['id'], amount, riskfactor=1, fuzzpercent=0)['route'] + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(route, payment_hash, payment_secret=inv['payment_secret']) # Amount should drop to 0 once HTLC is sent; we have time, thanks to @@ -2501,6 +2520,7 @@ def test_channel_spendable(node_factory, bitcoind): # Exact amount should succeed. route = l2.rpc.getroute(l1.info['id'], amount, riskfactor=1, fuzzpercent=0)['route'] + l2.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l2.rpc.sendpay(route, payment_hash, payment_secret=inv['payment_secret']) # Amount should drop to 0 once HTLC is sent; we have time, thanks to @@ -2663,6 +2683,7 @@ def test_htlc_too_dusty_outgoing(node_factory, bitcoind, chainparams): route = l1.rpc.getroute(l2.info['id'], non_dust_htlc_val_sat * 1000, 1)['route'] for i in range(0, 3): inv = l2.rpc.invoice((non_dust_htlc_val_sat * 1000), str(i + 100), str(i + 100)) + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret']) l2.daemon.wait_for_log(r'their htlc .* dev_ignore_htlcs') res = only_one(l1.rpc.listsendpays(payment_hash=inv['payment_hash'])['payments']) @@ -2672,6 +2693,7 @@ def test_htlc_too_dusty_outgoing(node_factory, bitcoind, chainparams): route = l1.rpc.getroute(l2.info['id'], htlc_val_msat, 1)['route'] for i in range(0, num_dusty_htlcs): inv = l2.rpc.invoice(htlc_val_msat, str(i), str(i)) + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret']) l2.daemon.wait_for_log(r'their htlc .* dev_ignore_htlcs') res = only_one(l1.rpc.listsendpays(payment_hash=inv['payment_hash'])['payments']) @@ -2679,6 +2701,7 @@ def test_htlc_too_dusty_outgoing(node_factory, bitcoind, chainparams): # one more should tip it over, and return a payment failure inv = l2.rpc.invoice(htlc_val_msat, str(num_dusty_htlcs), str(num_dusty_htlcs)) + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret']) l1.daemon.wait_for_log('CHANNEL_ERR_DUST_FAILURE') wait_for(lambda: only_one(l1.rpc.listsendpays(payment_hash=inv['payment_hash'])['payments'])['status'] == 'failed') @@ -2686,6 +2709,7 @@ def test_htlc_too_dusty_outgoing(node_factory, bitcoind, chainparams): # but we can still add a non dust htlc route = l1.rpc.getroute(l2.info['id'], non_dust_htlc_val_sat * 1000, 1)['route'] inv = l2.rpc.invoice((10000 * 1000), str(120), str(120)) + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret']) l2.daemon.wait_for_log(r'their htlc .* dev_ignore_htlcs') res = only_one(l1.rpc.listsendpays(payment_hash=inv['payment_hash'])['payments']) diff --git a/tests/test_plugin.py b/tests/test_plugin.py index b8868f9c1b7e..97d717dfd64c 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1300,12 +1300,14 @@ def test_forward_event_notification(node_factory, bitcoind, executor): route = l1.rpc.getroute(l3.info['id'], amount, 1)['route'] # status: offered -> settled + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(route, payment_hash13, payment_secret=inv['payment_secret']) l1.rpc.waitsendpay(payment_hash13) # status: offered -> failed route = l1.rpc.getroute(l4.info['id'], amount, 1)['route'] payment_hash14 = "f" * 64 + l1.rpc.preapprovekeysend(l4.info['id'], payment_hash14, amount) with pytest.raises(RpcError): l1.rpc.sendpay(route, payment_hash14, payment_secret="f" * 64) l1.rpc.waitsendpay(payment_hash14) @@ -1325,6 +1327,7 @@ def test_forward_event_notification(node_factory, bitcoind, executor): 'delay': 5, 'channel': c25}] + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming executor.submit(l1.rpc.sendpay, route, payment_hash15, payment_secret=inv['payment_secret']) l5.daemon.wait_for_log('permfail') @@ -1402,11 +1405,13 @@ def test_sendpay_notifications(node_factory, bitcoind): payment_hash2 = inv2['payment_hash'] route = l1.rpc.getroute(l3.info['id'], amount, 1)['route'] + l1.rpc.preapproveinvoice(bolt11=inv1['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(route, payment_hash1, payment_secret=inv1['payment_secret']) response1 = l1.rpc.waitsendpay(payment_hash1) l2.rpc.close(chanid23, 1) + l1.rpc.preapproveinvoice(bolt11=inv2['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(route, payment_hash2, payment_secret=inv2['payment_secret']) with pytest.raises(RpcError) as err: l1.rpc.waitsendpay(payment_hash2) @@ -1433,11 +1438,13 @@ def test_sendpay_notifications_nowaiter(node_factory): payment_hash2 = inv2['payment_hash'] route = l1.rpc.getroute(l3.info['id'], amount, 1)['route'] + l1.rpc.preapproveinvoice(bolt11=inv1['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(route, payment_hash1, payment_secret=inv1['payment_secret']) l1.daemon.wait_for_log(r'Received a sendpay_success') l2.rpc.close(chanid23, 1) + l1.rpc.preapproveinvoice(bolt11=inv2['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(route, payment_hash2, payment_secret=inv2['payment_secret']) l1.daemon.wait_for_log(r'Received a sendpay_failure') @@ -2017,12 +2024,14 @@ def test_coin_movement_notices(node_factory, bitcoind, chainparams): route = l1.rpc.getroute(l3.info['id'], amount, 1)['route'] # status: offered -> settled + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(route, payment_hash13, payment_secret=inv['payment_secret']) l1.rpc.waitsendpay(payment_hash13) # status: offered -> failed route = l1.rpc.getroute(l3.info['id'], amount, 1)['route'] payment_hash13 = "f" * 64 + l1.rpc.preapprovekeysend(l3.info['id'], payment_hash13, amount) with pytest.raises(RpcError): l1.rpc.sendpay(route, payment_hash13, payment_secret=inv['payment_secret']) l1.rpc.waitsendpay(payment_hash13) @@ -2031,6 +2040,7 @@ def test_coin_movement_notices(node_factory, bitcoind, chainparams): inv = l1.rpc.invoice(amount // 2, "first", "desc") payment_hash31 = inv['payment_hash'] route = l3.rpc.getroute(l1.info['id'], amount // 2, 1)['route'] + l3.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l3.rpc.sendpay(route, payment_hash31, payment_secret=inv['payment_secret']) l3.rpc.waitsendpay(payment_hash31) @@ -2038,6 +2048,7 @@ def test_coin_movement_notices(node_factory, bitcoind, chainparams): inv = l2.rpc.invoice(amount, "first", "desc") payment_hash12 = inv['payment_hash'] route = l1.rpc.getroute(l2.info['id'], amount, 1)['route'] + l1.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l1.rpc.sendpay(route, payment_hash12, payment_secret=inv['payment_secret']) l1.rpc.waitsendpay(payment_hash12) @@ -2045,6 +2056,7 @@ def test_coin_movement_notices(node_factory, bitcoind, chainparams): inv = l1.rpc.invoice(amount // 2, "second", "desc") payment_hash21 = inv['payment_hash'] route = l2.rpc.getroute(l1.info['id'], amount // 2, 1)['route'] + l2.rpc.preapproveinvoice(bolt11=inv['bolt11']) # let the signer know this payment is coming l2.rpc.sendpay(route, payment_hash21, payment_secret=inv['payment_secret']) l2.rpc.waitsendpay(payment_hash21) From cda5f60f3f1aba3ba3c2cf2373fd10b38b6b3c9a Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Thu, 8 Jun 2023 12:44:01 -0700 Subject: [PATCH 51/53] tests: Skip unless VLS_PERMISSIVE tests which violate policy --- tests/test_pay.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_pay.py b/tests/test_pay.py index d82a14beabaa..bc051da8fcf9 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -559,6 +559,7 @@ def test_pay_maxfee_shadow(node_factory): assert pay_status["amount_msat"] == Millisatoshi(amount) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "grossly overpaid invoice") def test_sendpay(node_factory): l1, l2 = node_factory.line_graph(2, fundamount=10**6) @@ -2473,6 +2474,7 @@ def test_setchannel_startup_opts(node_factory, bitcoind): assert result[1]['htlc_maximum_msat'] == Millisatoshi(5) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "invoice with any amount") @pytest.mark.developer("gossip without DEVELOPER=1 is slow") def test_channel_spendable(node_factory, bitcoind): """Test that spendable_msat is accurate""" @@ -2530,6 +2532,7 @@ def test_channel_spendable(node_factory, bitcoind): l2.rpc.waitsendpay(payment_hash, TIMEOUT) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "invoice with any amount") @pytest.mark.developer("gossip without DEVELOPER=1 is slow") def test_channel_receivable(node_factory, bitcoind): """Test that receivable_msat is accurate""" @@ -2586,6 +2589,7 @@ def test_channel_receivable(node_factory, bitcoind): l2.rpc.waitsendpay(payment_hash, TIMEOUT) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "invoice with any amount") @pytest.mark.developer("gossip without DEVELOPER=1 is slow") def test_channel_spendable_large(node_factory, bitcoind): """Test that spendable_msat is accurate for large channels""" From 94a57aae8b87d96fd38722b5591564d8f1b9100a Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Fri, 9 Jun 2023 16:05:56 -0700 Subject: [PATCH 52/53] tests: Skip entirely because malformed payment requests --- tests/test_pay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pay.py b/tests/test_pay.py index bc051da8fcf9..39edd88ef5e4 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -559,7 +559,7 @@ def test_pay_maxfee_shadow(node_factory): assert pay_status["amount_msat"] == Millisatoshi(amount) -@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd') and os.getenv('VLS_PERMISSIVE') != '1', "grossly overpaid invoice") +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "fails multiple policy checks") def test_sendpay(node_factory): l1, l2 = node_factory.line_graph(2, fundamount=10**6) From 70e6360a456fb89504413042b784edbee56a414f Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Fri, 9 Jun 2023 16:29:42 -0700 Subject: [PATCH 53/53] tests: skip as WORKAROUND for VLS #330 --- tests/test_pay.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_pay.py b/tests/test_pay.py index 39edd88ef5e4..c06f60812b90 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -151,6 +151,7 @@ def test_pay_limits(node_factory): assert status[0]['strategy'] == "Initial attempt" +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "skip until VLS #330 is fixed") @pytest.mark.developer("Gossip is too slow without developer") def test_pay_exclude_node(node_factory, bitcoind): """Test excluding the node if there's the NODE-level error in the failure_code @@ -1669,6 +1670,7 @@ def test_forward_local_failed_stats(node_factory, bitcoind, executor): assert [s.get('out_channel') for s in stats['forwards']] == [c23, c24, c25, None, c24] +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "skip until VLS #330 is fixed") @pytest.mark.developer("too slow without --dev-fast-gossip") @pytest.mark.slow_test def test_htlcs_cltv_only_difference(node_factory, bitcoind): @@ -1750,6 +1752,7 @@ def test_pay_variants(node_factory): l1.rpc.pay(b11) +@unittest.skipIf(os.getenv('SUBDAEMON').startswith('hsmd:remote_hsmd'), "skip until VLS #330 is fixed") @pytest.mark.developer("gossip without DEVELOPER=1 is slow") @pytest.mark.slow_test def test_pay_retry(node_factory, bitcoind, executor, chainparams):