2021-12-04 12:23:56 +01:00
# include "config.h"
2020-12-16 04:18:00 +01:00
# include <bitcoin/chainparams.h>
# include <ccan/array_size/array_size.h>
2021-09-30 23:19:36 +02:00
# include <ccan/cast/cast.h>
2020-12-16 04:18:00 +01:00
# include <ccan/json_out/json_out.h>
2020-12-16 04:18:20 +01:00
# include <ccan/mem/mem.h>
2020-12-16 04:18:42 +01:00
# include <ccan/str/hex/hex.h>
2020-12-16 04:18:00 +01:00
# include <ccan/tal/str/str.h>
# include <common/blindedpath.h>
# include <common/bolt12_merkle.h>
# include <common/dijkstra.h>
# include <common/gossmap.h>
2022-07-04 05:49:38 +02:00
# include <common/json_param.h>
2020-12-16 04:18:00 +01:00
# include <common/json_stream.h>
# include <common/memleak.h>
2020-12-16 04:18:20 +01:00
# include <common/overflows.h>
2020-12-16 04:18:00 +01:00
# include <common/route.h>
# include <common/type_to_string.h>
# include <errno.h>
# include <plugins/libplugin.h>
2020-12-16 04:18:20 +01:00
# include <secp256k1_schnorrsig.h>
2021-09-16 07:00:42 +02:00
# include <sodium.h>
2020-12-16 04:18:00 +01:00
static struct gossmap * global_gossmap ;
2021-09-21 07:23:45 +02:00
static struct pubkey local_id ;
2021-07-01 06:28:57 +02:00
static bool disable_connect = false ;
2020-12-16 04:18:20 +01:00
static LIST_HEAD ( sent_list ) ;
2020-12-16 04:18:00 +01:00
struct sent {
2020-12-16 04:18:20 +01:00
/* We're in sent_invreqs, awaiting reply. */
struct list_node list ;
2021-11-30 04:06:04 +01:00
/* The alias used by reply */
2021-09-30 23:19:36 +02:00
struct pubkey * reply_alias ;
2020-12-16 04:18:20 +01:00
/* The command which sent us. */
struct command * cmd ;
2021-01-07 19:38:47 +01:00
/* The offer we are trying to get an invoice/payment for. */
2020-12-16 04:18:00 +01:00
struct tlv_offer * offer ;
2021-09-21 07:23:45 +02:00
/* Path to use (including self) */
struct pubkey * path ;
2021-01-07 19:38:47 +01:00
/* The invreq we sent, OR the invoice we sent */
2020-12-16 04:18:00 +01:00
struct tlv_invoice_request * invreq ;
2021-01-07 19:38:47 +01:00
struct tlv_invoice * inv ;
struct preimage inv_preimage ;
struct json_escape * inv_label ;
2021-01-07 19:39:47 +01:00
/* How long to wait for response before giving up. */
2021-01-07 19:40:47 +01:00
u32 wait_timeout ;
2020-12-16 04:18:00 +01:00
} ;
2021-09-30 23:19:36 +02:00
static struct sent * find_sent_by_alias ( const struct pubkey * alias )
{
struct sent * i ;
list_for_each ( & sent_list , i , list ) {
if ( i - > reply_alias & & pubkey_eq ( i - > reply_alias , alias ) )
return i ;
}
return NULL ;
}
2021-07-05 08:23:29 +02:00
static const char * field_diff_ ( struct plugin * plugin ,
const tal_t * a , const tal_t * b ,
2020-12-16 04:18:20 +01:00
const char * fieldname )
{
/* One is set and the other isn't? */
2021-07-05 08:23:29 +02:00
if ( ( a = = NULL ) ! = ( b = = NULL ) ) {
plugin_log ( plugin , LOG_DBG , " field_diff %s: a is %s, b is %s " ,
fieldname , a ? " set " : " unset " , b ? " set " : " unset " ) ;
2020-12-16 04:18:20 +01:00
return fieldname ;
2021-07-05 08:23:29 +02:00
}
if ( ! memeq ( a , tal_bytelen ( a ) , b , tal_bytelen ( b ) ) ) {
plugin_log ( plugin , LOG_DBG , " field_diff %s: a=%s, b=%s " ,
fieldname , tal_hex ( tmpctx , a ) , tal_hex ( tmpctx , b ) ) ;
2020-12-16 04:18:20 +01:00
return fieldname ;
2021-07-05 08:23:29 +02:00
}
2020-12-16 04:18:20 +01:00
return NULL ;
}
2021-07-05 08:23:29 +02:00
# define field_diff(a, b, fieldname) \
field_diff_ ( ( cmd ) - > plugin , a - > fieldname , b - > fieldname , # fieldname )
2020-12-16 04:18:20 +01:00
/* Returns true if b is a with something appended. */
static bool description_is_appended ( const char * a , const char * b )
{
if ( ! a | | ! b )
return false ;
if ( tal_bytelen ( b ) < tal_bytelen ( a ) )
return false ;
return memeq ( a , tal_bytelen ( a ) , b , tal_bytelen ( a ) ) ;
}
/* Hack to suppress warnings when we finish a different command */
static void discard_result ( struct command_result * ret )
{
}
2021-01-07 19:38:47 +01:00
/* Returns NULL if it wasn't an error. */
static struct command_result * handle_error ( struct command * cmd ,
struct sent * sent ,
const char * buf ,
const jsmntok_t * om )
{
const u8 * data ;
size_t dlen ;
struct tlv_invoice_error * err ;
struct json_out * details ;
const jsmntok_t * errtok ;
errtok = json_get_member ( buf , om , " invoice_error " ) ;
if ( ! errtok )
return NULL ;
data = json_tok_bin_from_hex ( cmd , buf , errtok ) ;
dlen = tal_bytelen ( data ) ;
details = json_out_new ( cmd ) ;
plugin_log ( cmd - > plugin , LOG_DBG , " errtok = %.*s " ,
json_tok_full_len ( errtok ) ,
json_tok_full ( buf , errtok ) ) ;
json_out_start ( details , NULL , ' { ' ) ;
2022-03-23 00:31:14 +01:00
err = fromwire_tlv_invoice_error ( cmd , & data , & dlen ) ;
if ( ! err ) {
2021-01-07 19:38:47 +01:00
plugin_log ( cmd - > plugin , LOG_DBG ,
" Invalid invoice_error %.*s " ,
json_tok_full_len ( errtok ) ,
json_tok_full ( buf , errtok ) ) ;
json_out_addstr ( details , " invoice_error_hex " ,
tal_strndup ( tmpctx ,
buf + errtok - > start ,
errtok - > end - errtok - > start ) ) ;
} else {
char * failstr ;
/* FIXME: with a bit more generate-wire.py support,
* we could have fieldnames and even types . */
if ( err - > erroneous_field )
json_out_add ( details , " erroneous_field " , false ,
" % " PRIu64 , * err - > erroneous_field ) ;
if ( err - > suggested_value )
json_out_addstr ( details , " suggested_value " ,
tal_hex ( tmpctx ,
err - > suggested_value ) ) ;
/* If they don't include this, it'll be empty */
failstr = tal_strndup ( tmpctx ,
err - > error ,
tal_bytelen ( err - > error ) ) ;
json_out_addstr ( details , " error " , failstr ) ;
}
json_out_end ( details , ' } ' ) ;
discard_result ( command_done_err ( sent - > cmd ,
OFFER_BAD_INVREQ_REPLY ,
" Remote node sent failure message " ,
details ) ) ;
return command_hook_success ( cmd ) ;
}
static struct command_result * handle_invreq_response ( struct command * cmd ,
struct sent * sent ,
const char * buf ,
const jsmntok_t * om )
2020-12-16 04:18:20 +01:00
{
const u8 * invbin ;
2021-01-07 19:38:47 +01:00
const jsmntok_t * invtok ;
2020-12-16 04:18:20 +01:00
size_t len ;
struct tlv_invoice * inv ;
struct sha256 merkle , sighash ;
struct json_stream * out ;
const char * badfield ;
u64 * expected_amount ;
2020-12-16 04:18:42 +01:00
invtok = json_get_member ( buf , om , " invoice " ) ;
if ( ! invtok ) {
plugin_log ( cmd - > plugin , LOG_UNUSUAL ,
" Neither invoice nor invoice_request_failed in reply %.*s " ,
json_tok_full_len ( om ) ,
json_tok_full ( buf , om ) ) ;
discard_result ( command_fail ( sent - > cmd ,
OFFER_BAD_INVREQ_REPLY ,
" Neither invoice nor invoice_request_failed in reply %.*s " ,
json_tok_full_len ( om ) ,
json_tok_full ( buf , om ) ) ) ;
return command_hook_success ( cmd ) ;
}
2020-12-16 04:18:20 +01:00
invbin = json_tok_bin_from_hex ( cmd , buf , invtok ) ;
len = tal_bytelen ( invbin ) ;
2022-03-23 00:31:14 +01:00
inv = fromwire_tlv_invoice ( cmd , & invbin , & len ) ;
if ( ! inv ) {
2020-12-16 04:18:20 +01:00
badfield = " invoice " ;
goto badinv ;
}
2021-08-19 06:53:04 +02:00
# if DEVELOPER
/* Raw send? Just fwd reply. */
if ( ! sent - > offer ) {
out = jsonrpc_stream_success ( sent - > cmd ) ;
json_add_string ( out , " invoice " , invoice_encode ( tmpctx , inv ) ) ;
discard_result ( command_finished ( sent - > cmd , out ) ) ;
return command_hook_success ( cmd ) ;
}
# endif /* DEVELOPER */
2020-12-16 04:18:20 +01:00
/* BOLT-offers #12:
* - MUST reject the invoice unless ` node_id ` is equal to the offer .
*/
2021-10-08 00:54:42 +02:00
if ( ! point32_eq ( sent - > offer - > node_id , inv - > node_id ) ) {
2020-12-16 04:18:20 +01:00
badfield = " node_id " ;
goto badinv ;
}
/* BOLT-offers #12:
* - MUST reject the invoice if ` signature ` is not a valid signature
* using ` node_id ` as described in [ Signature Calculation ]
*/
merkle_tlv ( inv - > fields , & merkle ) ;
sighash_from_merkle ( " invoice " , " signature " , & merkle , & sighash ) ;
if ( ! inv - > signature
| | secp256k1_schnorrsig_verify ( secp256k1_ctx , inv - > signature - > u8 ,
2022-04-07 01:54:52 +02:00
sighash . u . u8 , sizeof ( sighash . u . u8 ) , & inv - > node_id - > pubkey ) ! = 1 ) {
2020-12-16 04:18:20 +01:00
badfield = " signature " ;
goto badinv ;
}
/* BOLT-offers #12:
* - MUST reject the invoice if ` msat ` is not present .
*/
if ( ! inv - > amount ) {
badfield = " amount " ;
goto badinv ;
}
/* BOLT-offers #12:
* - MUST reject the invoice unless ` offer_id ` is equal to the id of the
* offer .
*/
if ( ( badfield = field_diff ( sent - > invreq , inv , offer_id ) ) )
goto badinv ;
/* BOLT-offers #12:
2021-11-30 04:06:05 +01:00
* - if the invoice is a reply to an ` invoice_request ` :
* . . .
* - MUST reject the invoice unless the following fields are equal or
* unset exactly as they are in the ` invoice_request : `
* - ` quantity `
* - ` payer_key `
* - ` payer_info `
*/
/* BOLT-offers-recurrence #12:
2020-12-16 04:18:20 +01:00
* - if the invoice is a reply to an ` invoice_request ` :
* . . .
* - MUST reject the invoice unless the following fields are equal or
* unset exactly as they are in the ` invoice_request : `
* - ` quantity `
* - ` recurrence_counter `
* - ` recurrence_start `
* - ` payer_key `
* - ` payer_info `
*/
if ( ( badfield = field_diff ( sent - > invreq , inv , quantity ) ) )
goto badinv ;
if ( ( badfield = field_diff ( sent - > invreq , inv , recurrence_counter ) ) )
goto badinv ;
if ( ( badfield = field_diff ( sent - > invreq , inv , recurrence_start ) ) )
goto badinv ;
if ( ( badfield = field_diff ( sent - > invreq , inv , payer_key ) ) )
goto badinv ;
if ( ( badfield = field_diff ( sent - > invreq , inv , payer_info ) ) )
goto badinv ;
2021-07-21 07:37:39 +02:00
/* Get the amount we expected: firstly, if that's what we sent,
* secondly , if specified in the invoice . */
if ( sent - > invreq - > amount ) {
expected_amount = tal_dup ( tmpctx , u64 , sent - > invreq - > amount ) ;
} else if ( sent - > offer - > amount & & ! sent - > offer - > currency ) {
2020-12-16 04:18:20 +01:00
expected_amount = tal ( tmpctx , u64 ) ;
* expected_amount = * sent - > offer - > amount ;
if ( sent - > invreq - > quantity ) {
/* We should never have sent this! */
if ( mul_overflows_u64 ( * expected_amount ,
* sent - > invreq - > quantity ) ) {
badfield = " quantity overflow " ;
goto badinv ;
}
* expected_amount * = * sent - > invreq - > quantity ;
}
} else
expected_amount = NULL ;
2021-11-30 04:06:05 +01:00
/* BOLT-offers-recurrence #12:
2020-12-16 04:18:42 +01:00
* - if the offer contained ` recurrence ` :
* - MUST reject the invoice if ` recurrence_basetime ` is not set .
*/
if ( sent - > invreq - > recurrence_counter & & ! inv - > recurrence_basetime ) {
badfield = " recurrence_basetime " ;
goto badinv ;
}
2020-12-16 04:18:20 +01:00
/* BOLT-offers #12:
* - SHOULD confirm authorization if the ` description ` does not exactly
* match the ` offer `
* - MAY highlight if ` description ` has simply had a change appended .
*/
/* We highlight these changes to the caller, for them to handle */
out = jsonrpc_stream_success ( sent - > cmd ) ;
json_add_string ( out , " invoice " , invoice_encode ( tmpctx , inv ) ) ;
json_object_start ( out , " changes " ) ;
if ( field_diff ( sent - > offer , inv , description ) ) {
/* Did they simply append? */
if ( description_is_appended ( sent - > offer - > description ,
inv - > description ) ) {
size_t off = tal_bytelen ( sent - > offer - > description ) ;
json_add_stringn ( out , " description_appended " ,
inv - > description + off ,
tal_bytelen ( inv - > description ) - off ) ;
} else if ( ! inv - > description )
json_add_stringn ( out , " description_removed " ,
sent - > offer - > description ,
tal_bytelen ( sent - > offer - > description ) ) ;
else
json_add_stringn ( out , " description " ,
inv - > description ,
tal_bytelen ( inv - > description ) ) ;
}
/* BOLT-offers #12:
2021-10-08 02:52:34 +02:00
* - SHOULD confirm authorization if ` issuer ` does not exactly
2020-12-16 04:18:20 +01:00
* match the ` offer `
*/
2021-10-08 02:52:34 +02:00
if ( field_diff ( sent - > offer , inv , issuer ) ) {
if ( ! inv - > issuer )
json_add_stringn ( out , " issuer_removed " ,
sent - > offer - > issuer ,
tal_bytelen ( sent - > offer - > issuer ) ) ;
2020-12-16 04:18:20 +01:00
else
2021-10-08 02:52:34 +02:00
json_add_stringn ( out , " issuer " ,
inv - > issuer ,
tal_bytelen ( inv - > issuer ) ) ;
2020-12-16 04:18:20 +01:00
}
/* BOLT-offers #12:
* - SHOULD confirm authorization if ` msat ` is not within the amount
* range authorized .
*/
/* We always tell them this unless it's trivial to calc and
* exactly as expected . */
2022-06-20 12:22:09 +02:00
if ( ! expected_amount | | * inv - > amount ! = * expected_amount ) {
if ( deprecated_apis )
json_add_amount_msat_only ( out , " msat " ,
amount_msat ( * inv - > amount ) ) ;
json_add_amount_msat_only ( out , " amount_msat " ,
2020-12-16 04:18:20 +01:00
amount_msat ( * inv - > amount ) ) ;
2022-06-20 12:22:09 +02:00
}
2020-12-16 04:18:20 +01:00
json_object_end ( out ) ;
2020-12-16 04:18:42 +01:00
/* We tell them about next period at this point, if any. */
if ( sent - > offer - > recurrence ) {
u64 next_counter , next_period_idx ;
u64 paywindow_start , paywindow_end ;
next_counter = * sent - > invreq - > recurrence_counter + 1 ;
if ( sent - > invreq - > recurrence_start )
next_period_idx = * sent - > invreq - > recurrence_start
+ next_counter ;
else
next_period_idx = next_counter ;
/* If this was the last, don't tell them about a next! */
if ( ! sent - > offer - > recurrence_limit
| | next_period_idx < = * sent - > offer - > recurrence_limit ) {
json_object_start ( out , " next_period " ) ;
json_add_u64 ( out , " counter " , next_counter ) ;
json_add_u64 ( out , " starttime " ,
offer_period_start ( * inv - > recurrence_basetime ,
next_period_idx ,
sent - > offer - > recurrence ) ) ;
json_add_u64 ( out , " endtime " ,
offer_period_start ( * inv - > recurrence_basetime ,
next_period_idx + 1 ,
sent - > offer - > recurrence ) - 1 ) ;
offer_period_paywindow ( sent - > offer - > recurrence ,
sent - > offer - > recurrence_paywindow ,
sent - > offer - > recurrence_base ,
* inv - > recurrence_basetime ,
next_period_idx ,
& paywindow_start , & paywindow_end ) ;
json_add_u64 ( out , " paywindow_start " , paywindow_start ) ;
json_add_u64 ( out , " paywindow_end " , paywindow_end ) ;
json_object_end ( out ) ;
}
}
2020-12-16 04:18:20 +01:00
discard_result ( command_finished ( sent - > cmd , out ) ) ;
return command_hook_success ( cmd ) ;
badinv :
plugin_log ( cmd - > plugin , LOG_DBG , " Failed invoice due to %s " , badfield ) ;
discard_result ( command_fail ( sent - > cmd ,
OFFER_BAD_INVREQ_REPLY ,
" Incorrect %s field in %.*s " ,
badfield ,
json_tok_full_len ( invtok ) ,
json_tok_full ( buf , invtok ) ) ) ;
return command_hook_success ( cmd ) ;
}
2021-09-30 23:19:36 +02:00
static struct command_result * recv_modern_onion_message ( struct command * cmd ,
const char * buf ,
const jsmntok_t * params )
{
const jsmntok_t * om , * aliastok ;
struct sent * sent ;
struct pubkey alias ;
struct command_result * err ;
om = json_get_member ( buf , params , " onion_message " ) ;
aliastok = json_get_member ( buf , om , " our_alias " ) ;
if ( ! aliastok | | ! json_to_pubkey ( buf , aliastok , & alias ) )
return command_hook_success ( cmd ) ;
sent = find_sent_by_alias ( & alias ) ;
if ( ! sent ) {
plugin_log ( cmd - > plugin , LOG_DBG ,
" No match for modern onion %.*s " ,
json_tok_full_len ( om ) ,
json_tok_full ( buf , om ) ) ;
return command_hook_success ( cmd ) ;
}
plugin_log ( cmd - > plugin , LOG_DBG , " Received modern onion message: %.*s " ,
json_tok_full_len ( params ) ,
json_tok_full ( buf , params ) ) ;
err = handle_error ( cmd , sent , buf , om ) ;
if ( err )
return err ;
if ( sent - > invreq )
return handle_invreq_response ( cmd , sent , buf , om ) ;
return command_hook_success ( cmd ) ;
}
2020-12-16 04:18:20 +01:00
static void destroy_sent ( struct sent * sent )
{
list_del ( & sent - > list ) ;
}
2021-01-07 19:40:47 +01:00
/* We've received neither a reply nor a payment; return failure. */
static void timeout_sent_invreq ( struct sent * sent )
{
/* This will free sent! */
discard_result ( command_fail ( sent - > cmd , OFFER_TIMEOUT ,
" Timeout waiting for response " ) ) ;
}
2020-12-16 04:18:00 +01:00
static struct command_result * sendonionmsg_done ( struct command * cmd ,
const char * buf UNUSED ,
const jsmntok_t * result UNUSED ,
struct sent * sent )
{
2021-01-07 19:40:47 +01:00
tal_steal ( cmd , plugin_timer ( cmd - > plugin ,
time_from_sec ( sent - > wait_timeout ) ,
timeout_sent_invreq , sent ) ) ;
2020-12-16 04:18:20 +01:00
sent - > cmd = cmd ;
list_add_tail ( & sent_list , & sent - > list ) ;
tal_add_destructor ( sent , destroy_sent ) ;
2020-12-16 04:18:00 +01:00
return command_still_pending ( cmd ) ;
}
static void init_gossmap ( struct plugin * plugin )
{
2021-08-20 06:12:27 +02:00
size_t num_cupdates_rejected ;
2020-12-16 04:18:00 +01:00
global_gossmap
= notleak_with_children ( gossmap_load ( NULL ,
2021-08-20 06:12:27 +02:00
GOSSIP_STORE_FILENAME ,
& num_cupdates_rejected ) ) ;
2020-12-16 04:18:00 +01:00
if ( ! global_gossmap )
plugin_err ( plugin , " Could not load gossmap %s: %s " ,
GOSSIP_STORE_FILENAME , strerror ( errno ) ) ;
2021-08-20 06:12:27 +02:00
if ( num_cupdates_rejected )
plugin_log ( plugin , LOG_DBG ,
" gossmap ignored %zu channel updates " ,
num_cupdates_rejected ) ;
2020-12-16 04:18:00 +01:00
}
static struct gossmap * get_gossmap ( struct plugin * plugin )
{
if ( ! global_gossmap )
init_gossmap ( plugin ) ;
else
2021-08-20 06:12:27 +02:00
gossmap_refresh ( global_gossmap , NULL ) ;
2020-12-16 04:18:00 +01:00
return global_gossmap ;
}
static struct command_result * param_offer ( struct command * cmd ,
const char * name ,
const char * buffer ,
const jsmntok_t * tok ,
struct tlv_offer * * offer )
{
char * fail ;
/* BOLT-offers #12:
* - if ` features ` contains unknown _odd_ bits that are non - zero :
* - MUST ignore the bit .
* - if ` features ` contains unknown _even_ bits that are non - zero :
* - MUST NOT respond to the offer .
* - SHOULD indicate the unknown bit to the user .
*/
/* BOLT-offers #12:
* - MUST NOT set or imply any ` chain_hash ` not set or implied by
* the offer .
*/
* offer = offer_decode ( cmd , buffer + tok - > start , tok - > end - tok - > start ,
plugin_feature_set ( cmd - > plugin ) , chainparams ,
& fail ) ;
if ( ! * offer )
return command_fail_badparam ( cmd , name , buffer , tok ,
tal_fmt ( cmd ,
" Unparsable offer: %s " ,
fail ) ) ;
/* BOLT-offers #12:
*
2021-07-05 13:27:39 +02:00
* - if ` node_id ` or ` description ` is not set :
2020-12-16 04:18:00 +01:00
* - MUST NOT respond to the offer .
*/
if ( ! ( * offer ) - > node_id )
return command_fail_badparam ( cmd , name , buffer , tok ,
" Offer does not contain a node_id " ) ;
if ( ! ( * offer ) - > description )
return command_fail_badparam ( cmd , name , buffer , tok ,
" Offer does not contain a description " ) ;
return NULL ;
}
static bool can_carry_onionmsg ( const struct gossmap * map ,
const struct gossmap_chan * c ,
int dir ,
struct amount_msat amount UNUSED ,
void * arg UNUSED )
{
const struct gossmap_node * n ;
/* Don't use it if either side says it's disabled */
if ( ! c - > half [ dir ] . enabled | | ! c - > half [ ! dir ] . enabled )
return false ;
/* Check features of recipient */
n = gossmap_nth_node ( map , c , ! dir ) ;
2021-07-02 02:11:30 +02:00
/* 102/103 was the old EXPERIMENTAL feature bit: remove soon! */
return gossmap_node_get_feature ( map , n , OPT_ONION_MESSAGES ) ! = - 1
| | gossmap_node_get_feature ( map , n , 102 ) ! = - 1 ;
2020-12-16 04:18:00 +01:00
}
2021-09-21 06:05:24 +02:00
enum nodeid_parity {
nodeid_parity_even = SECP256K1_TAG_PUBKEY_EVEN ,
nodeid_parity_odd = SECP256K1_TAG_PUBKEY_ODD ,
nodeid_parity_unknown = 1 ,
} ;
static enum nodeid_parity node_parity ( const struct gossmap * gossmap ,
const struct gossmap_node * node )
{
struct node_id id ;
gossmap_node_get_id ( gossmap , node , & id ) ;
return id . k [ 0 ] ;
}
2021-10-08 00:54:42 +02:00
static void node_id_from_point32 ( struct node_id * nid ,
const struct point32 * node32_id ,
2021-09-21 06:05:24 +02:00
enum nodeid_parity parity )
{
assert ( parity = = SECP256K1_TAG_PUBKEY_EVEN
| | parity = = SECP256K1_TAG_PUBKEY_ODD ) ;
nid - > k [ 0 ] = parity ;
secp256k1_xonly_pubkey_serialize ( secp256k1_ctx , nid - > k + 1 ,
& node32_id - > pubkey ) ;
}
2021-09-21 07:23:45 +02:00
/* Create path to node which can carry onion messages (including
* self ) ; if it can ' t find one , returns NULL . Fills in nodeid_parity
* for 33 rd nodeid byte . */
static struct pubkey * path_to_node ( const tal_t * ctx ,
struct plugin * plugin ,
2021-10-08 00:54:42 +02:00
const struct point32 * node32_id ,
2021-09-21 07:23:45 +02:00
enum nodeid_parity * parity )
2020-12-16 04:18:00 +01:00
{
2021-09-21 06:05:24 +02:00
struct route_hop * r ;
const struct dijkstra * dij ;
const struct gossmap_node * src ;
2021-01-07 19:44:47 +01:00
const struct gossmap_node * dst ;
2021-09-21 07:23:45 +02:00
struct node_id dstid , local_nodeid ;
struct pubkey * nodes ;
struct gossmap * gossmap = get_gossmap ( plugin ) ;
2020-12-16 04:18:00 +01:00
2021-09-21 06:05:24 +02:00
/* We try both parities. */
* parity = nodeid_parity_even ;
2021-10-08 00:54:42 +02:00
node_id_from_point32 ( & dstid , node32_id , * parity ) ;
2020-12-16 04:18:00 +01:00
dst = gossmap_find_node ( gossmap , & dstid ) ;
2021-01-07 19:44:47 +01:00
if ( ! dst ) {
2021-09-21 06:05:24 +02:00
* parity = nodeid_parity_odd ;
2021-10-08 00:54:42 +02:00
node_id_from_point32 ( & dstid , node32_id , * parity ) ;
2021-09-21 06:05:24 +02:00
dst = gossmap_find_node ( gossmap , & dstid ) ;
if ( ! dst ) {
* parity = nodeid_parity_unknown ;
return NULL ;
}
2021-07-01 06:28:57 +02:00
}
2021-05-22 09:10:01 +02:00
2021-09-21 06:05:24 +02:00
* parity = node_parity ( gossmap , dst ) ;
/* If we don't exist in gossip, routing can't happen. */
2021-09-21 07:23:45 +02:00
node_id_from_pubkey ( & local_nodeid , & local_id ) ;
src = gossmap_find_node ( gossmap , & local_nodeid ) ;
2021-09-21 06:05:24 +02:00
if ( ! src )
return NULL ;
dij = dijkstra ( tmpctx , gossmap , dst , AMOUNT_MSAT ( 0 ) , 0 ,
can_carry_onionmsg , route_score_shorter , NULL ) ;
r = route_from_dijkstra ( tmpctx , gossmap , dij , src , AMOUNT_MSAT ( 0 ) , 0 ) ;
if ( ! r )
return NULL ;
2021-09-21 07:23:45 +02:00
nodes = tal_arr ( ctx , struct pubkey , tal_count ( r ) + 1 ) ;
nodes [ 0 ] = local_id ;
for ( size_t i = 0 ; i < tal_count ( r ) ; i + + ) {
if ( ! pubkey_from_node_id ( & nodes [ i + 1 ] , & r [ i ] . node_id ) ) {
plugin_err ( plugin , " Could not convert nodeid %s " ,
type_to_string ( tmpctx , struct node_id ,
& r [ i ] . node_id ) ) ;
}
}
2021-07-01 06:28:57 +02:00
return nodes ;
}
2021-11-30 04:06:04 +01:00
/* Marshal arguments for sending onion messages */
2021-09-30 23:19:36 +02:00
struct sending {
struct sent * sent ;
2022-03-28 01:10:54 +02:00
const char * msgfield ;
const u8 * msgval ;
struct tlv_obs2_onionmsg_payload_reply_path * obs2_reply_path ;
2021-09-30 23:19:36 +02:00
struct command_result * ( * done ) ( struct command * cmd ,
const char * buf UNUSED ,
const jsmntok_t * result UNUSED ,
struct sent * sent ) ;
} ;
2022-03-28 01:10:54 +02:00
static struct command_result *
send_obs2_message ( struct command * cmd ,
const char * buf ,
const jsmntok_t * result ,
struct sending * sending )
{
struct sent * sent = sending - > sent ;
struct privkey blinding_iter ;
struct pubkey fwd_blinding , * node_alias ;
size_t nhops = tal_count ( sent - > path ) ;
struct tlv_obs2_onionmsg_payload * * payloads ;
struct out_req * req ;
/* Now create enctlvs for *forward* path. */
randombytes_buf ( & blinding_iter , sizeof ( blinding_iter ) ) ;
if ( ! pubkey_from_privkey ( & blinding_iter , & fwd_blinding ) )
return command_fail ( cmd , LIGHTNINGD ,
" Could not convert blinding %s to pubkey! " ,
type_to_string ( tmpctx , struct privkey ,
& blinding_iter ) ) ;
/* We overallocate: this node (0) doesn't have payload or alias */
payloads = tal_arr ( cmd , struct tlv_obs2_onionmsg_payload * , nhops ) ;
node_alias = tal_arr ( cmd , struct pubkey , nhops ) ;
for ( size_t i = 1 ; i < nhops - 1 ; i + + ) {
payloads [ i ] = tlv_obs2_onionmsg_payload_new ( payloads ) ;
payloads [ i ] - > enctlv = create_obs2_enctlv ( payloads [ i ] ,
& blinding_iter ,
& sent - > path [ i ] ,
& sent - > path [ i + 1 ] ,
/* FIXME: Pad? */
0 ,
NULL ,
& blinding_iter ,
& node_alias [ i ] ) ;
}
/* Final payload contains the actual data. */
payloads [ nhops - 1 ] = tlv_obs2_onionmsg_payload_new ( payloads ) ;
/* We don't include enctlv in final, but it gives us final alias */
if ( ! create_obs2_final_enctlv ( tmpctx , & blinding_iter , & sent - > path [ nhops - 1 ] ,
/* FIXME: Pad? */ 0 ,
NULL ,
& node_alias [ nhops - 1 ] ) ) {
/* Should not happen! */
return command_fail ( cmd , LIGHTNINGD ,
" Could create final enctlv " ) ;
}
/* FIXME: This interface is a string for sendobsonionmessage! */
if ( streq ( sending - > msgfield , " invoice_request " ) ) {
payloads [ nhops - 1 ] - > invoice_request
= cast_const ( u8 * , sending - > msgval ) ;
} else {
assert ( streq ( sending - > msgfield , " invoice " ) ) ;
payloads [ nhops - 1 ] - > invoice
= cast_const ( u8 * , sending - > msgval ) ;
}
payloads [ nhops - 1 ] - > reply_path = sending - > obs2_reply_path ;
req = jsonrpc_request_start ( cmd - > plugin , cmd , " sendobs2onionmessage " ,
sending - > done ,
forward_error ,
sending - > sent ) ;
json_add_pubkey ( req - > js , " first_id " , & sent - > path [ 1 ] ) ;
json_add_pubkey ( req - > js , " blinding " , & fwd_blinding ) ;
json_array_start ( req - > js , " hops " ) ;
for ( size_t i = 1 ; i < nhops ; i + + ) {
u8 * tlv ;
json_object_start ( req - > js , NULL ) ;
json_add_pubkey ( req - > js , " id " , & node_alias [ i ] ) ;
tlv = tal_arr ( tmpctx , u8 , 0 ) ;
towire_tlv_obs2_onionmsg_payload ( & tlv , payloads [ i ] ) ;
json_add_hex_talarr ( req - > js , " tlv " , tlv ) ;
json_object_end ( req - > js ) ;
}
json_array_end ( req - > js ) ;
return send_outreq ( cmd - > plugin , req ) ;
}
2021-11-30 04:06:05 +01:00
static struct command_result *
send_modern_message ( struct command * cmd ,
struct tlv_onionmsg_payload_reply_path * reply_path ,
struct sending * sending )
{
struct sent * sent = sending - > sent ;
struct privkey blinding_iter ;
struct pubkey fwd_blinding , * node_alias ;
size_t nhops = tal_count ( sent - > path ) ;
struct tlv_onionmsg_payload * * payloads ;
struct out_req * req ;
/* Now create enctlvs for *forward* path. */
randombytes_buf ( & blinding_iter , sizeof ( blinding_iter ) ) ;
if ( ! pubkey_from_privkey ( & blinding_iter , & fwd_blinding ) )
return command_fail ( cmd , LIGHTNINGD ,
" Could not convert blinding %s to pubkey! " ,
type_to_string ( tmpctx , struct privkey ,
& blinding_iter ) ) ;
/* We overallocate: this node (0) doesn't have payload or alias */
payloads = tal_arr ( cmd , struct tlv_onionmsg_payload * , nhops ) ;
node_alias = tal_arr ( cmd , struct pubkey , nhops ) ;
for ( size_t i = 1 ; i < nhops - 1 ; i + + ) {
payloads [ i ] = tlv_onionmsg_payload_new ( payloads ) ;
payloads [ i ] - > encrypted_data_tlv = create_enctlv ( payloads [ i ] ,
& blinding_iter ,
& sent - > path [ i ] ,
& sent - > path [ i + 1 ] ,
/* FIXME: Pad? */
0 ,
NULL ,
& blinding_iter ,
& node_alias [ i ] ) ;
}
/* Final payload contains the actual data. */
2022-03-28 01:10:54 +02:00
payloads [ nhops - 1 ] = tlv_onionmsg_payload_new ( payloads ) ;
2021-11-30 04:06:05 +01:00
/* We don't include enctlv in final, but it gives us final alias */
if ( ! create_final_enctlv ( tmpctx , & blinding_iter , & sent - > path [ nhops - 1 ] ,
/* FIXME: Pad? */ 0 ,
NULL ,
& node_alias [ nhops - 1 ] ) ) {
/* Should not happen! */
return command_fail ( cmd , LIGHTNINGD ,
" Could create final enctlv " ) ;
}
2022-03-28 01:10:54 +02:00
/* FIXME: This interface is a string for sendobsonionmessage! */
if ( streq ( sending - > msgfield , " invoice_request " ) ) {
payloads [ nhops - 1 ] - > invoice_request
= cast_const ( u8 * , sending - > msgval ) ;
} else {
assert ( streq ( sending - > msgfield , " invoice " ) ) ;
payloads [ nhops - 1 ] - > invoice
= cast_const ( u8 * , sending - > msgval ) ;
}
2021-11-30 04:06:05 +01:00
payloads [ nhops - 1 ] - > reply_path = reply_path ;
req = jsonrpc_request_start ( cmd - > plugin , cmd , " sendonionmessage " ,
2022-03-28 01:10:54 +02:00
/* Try sending older version next */
send_obs2_message ,
2021-11-30 04:06:05 +01:00
forward_error ,
2022-03-28 01:10:54 +02:00
sending ) ;
2021-11-30 04:06:05 +01:00
json_add_pubkey ( req - > js , " first_id " , & sent - > path [ 1 ] ) ;
json_add_pubkey ( req - > js , " blinding " , & fwd_blinding ) ;
json_array_start ( req - > js , " hops " ) ;
for ( size_t i = 1 ; i < nhops ; i + + ) {
u8 * tlv ;
json_object_start ( req - > js , NULL ) ;
json_add_pubkey ( req - > js , " id " , & node_alias [ i ] ) ;
tlv = tal_arr ( tmpctx , u8 , 0 ) ;
2022-03-23 00:31:14 +01:00
towire_tlv_onionmsg_payload ( & tlv , payloads [ i ] ) ;
2021-11-30 04:06:05 +01:00
json_add_hex_talarr ( req - > js , " tlv " , tlv ) ;
json_object_end ( req - > js ) ;
}
json_array_end ( req - > js ) ;
return send_outreq ( cmd - > plugin , req ) ;
}
2021-09-30 23:19:36 +02:00
/* Lightningd gives us reply path, since we don't know secret to put
* in final so it will recognize it . */
static struct command_result * use_reply_path ( struct command * cmd ,
const char * buf ,
const jsmntok_t * result ,
struct sending * sending )
{
2021-11-30 04:06:05 +01:00
struct tlv_onionmsg_payload_reply_path * rpath ;
2021-09-30 23:19:36 +02:00
2021-11-30 04:06:05 +01:00
rpath = json_to_reply_path ( cmd , buf ,
json_get_member ( buf , result , " blindedpath " ) ) ;
2021-09-30 23:19:36 +02:00
if ( ! rpath )
plugin_err ( cmd - > plugin ,
" could not parse reply path %.*s? " ,
json_tok_full_len ( result ) ,
2021-11-30 04:06:05 +01:00
json_tok_full ( buf , result ) ) ;
2022-03-28 01:10:54 +02:00
sending - > obs2_reply_path = json_to_obs2_reply_path ( cmd , buf ,
json_get_member ( buf , result ,
" obs2blindedpath " ) ) ;
if ( ! sending - > obs2_reply_path )
plugin_err ( cmd - > plugin ,
" could not parse obs2 reply path %.*s? " ,
json_tok_full_len ( result ) ,
json_tok_full ( buf , result ) ) ;
2021-09-30 23:19:36 +02:00
/* Remember our alias we used so we can recognize reply */
sending - > sent - > reply_alias
= tal_dup ( sending - > sent , struct pubkey ,
& rpath - > path [ tal_count ( rpath - > path ) - 1 ] - > node_id ) ;
return send_modern_message ( cmd , rpath , sending ) ;
}
static struct command_result * make_reply_path ( struct command * cmd ,
struct sending * sending )
{
struct out_req * req ;
size_t nhops = tal_count ( sending - > sent - > path ) ;
/* FIXME: Maybe we should allow this? */
if ( tal_count ( sending - > sent - > path ) = = 1 )
return command_fail ( cmd , PAY_ROUTE_NOT_FOUND ,
" Refusing to talk to ourselves " ) ;
req = jsonrpc_request_start ( cmd - > plugin , cmd , " blindedpath " ,
use_reply_path ,
forward_error ,
sending ) ;
/* FIXME: Could create an independent reply path, not just
* reverse existing . */
json_array_start ( req - > js , " ids " ) ;
for ( int i = nhops - 2 ; i > = 0 ; i - - )
json_add_pubkey ( req - > js , NULL , & sending - > sent - > path [ i ] ) ;
json_array_end ( req - > js ) ;
return send_outreq ( cmd - > plugin , req ) ;
}
static struct command_result * send_message ( struct command * cmd ,
struct sent * sent ,
2022-03-28 01:10:54 +02:00
const char * msgfield TAKES ,
const u8 * msgval TAKES ,
2021-09-30 23:19:36 +02:00
struct command_result * ( * done )
( struct command * cmd ,
const char * buf UNUSED ,
const jsmntok_t * result UNUSED ,
struct sent * sent ) )
{
struct sending * sending = tal ( cmd , struct sending ) ;
sending - > sent = sent ;
2022-03-28 01:10:54 +02:00
sending - > msgfield = tal_strdup ( sending , msgfield ) ;
sending - > msgval = tal_dup_talarr ( sending , u8 , msgval ) ;
2021-09-30 23:19:36 +02:00
sending - > done = done ;
return make_reply_path ( cmd , sending ) ;
}
2021-01-07 19:39:47 +01:00
/* We've received neither a reply nor a payment; return failure. */
static void timeout_sent_inv ( struct sent * sent )
{
struct json_out * details = json_out_new ( sent ) ;
2021-05-21 07:20:05 +02:00
json_out_start ( details , NULL , ' { ' ) ;
2021-01-07 19:39:47 +01:00
json_out_addstr ( details , " invstring " , invoice_encode ( tmpctx , sent - > inv ) ) ;
2021-05-21 07:20:05 +02:00
json_out_end ( details , ' } ' ) ;
2021-01-07 19:39:47 +01:00
/* This will free sent! */
discard_result ( command_done_err ( sent - > cmd , OFFER_TIMEOUT ,
2021-01-07 19:53:47 +01:00
" Failed: timeout waiting for response " ,
2021-01-07 19:39:47 +01:00
details ) ) ;
}
static struct command_result * prepare_inv_timeout ( struct command * cmd ,
const char * buf UNUSED ,
const jsmntok_t * result UNUSED ,
struct sent * sent )
{
tal_steal ( cmd , plugin_timer ( cmd - > plugin ,
2021-01-07 19:40:47 +01:00
time_from_sec ( sent - > wait_timeout ) ,
2021-01-07 19:39:47 +01:00
timeout_sent_inv , sent ) ) ;
return sendonionmsg_done ( cmd , buf , result , sent ) ;
}
2021-07-01 06:28:57 +02:00
/* We've connected (if we tried), so send the invreq. */
static struct command_result *
sendinvreq_after_connect ( struct command * cmd ,
const char * buf UNUSED ,
const jsmntok_t * result UNUSED ,
struct sent * sent )
{
2022-03-28 01:10:54 +02:00
u8 * rawinvreq = tal_arr ( tmpctx , u8 , 0 ) ;
towire_tlv_invoice_request ( & rawinvreq , sent - > invreq ) ;
2022-03-23 00:31:14 +01:00
2022-03-28 01:10:54 +02:00
return send_message ( cmd , sent , " invoice_request " , rawinvreq ,
sendonionmsg_done ) ;
2021-07-01 06:28:57 +02:00
}
2021-09-21 06:05:24 +02:00
struct connect_attempt {
struct node_id node_id ;
struct command_result * ( * cb ) ( struct command * command ,
const char * buf ,
const jsmntok_t * result ,
struct sent * sent ) ;
struct sent * sent ;
} ;
static struct command_result * connected ( struct command * command ,
const char * buf ,
const jsmntok_t * result ,
struct connect_attempt * ca )
{
return ca - > cb ( command , buf , result , ca - > sent ) ;
}
static struct command_result * connect_failed ( struct command * command ,
const char * buf ,
const jsmntok_t * result ,
struct connect_attempt * ca )
{
return command_done_err ( command , OFFER_ROUTE_NOT_FOUND ,
" Failed: could not route, could not connect " ,
NULL ) ;
}
/* Offers contain only a 32-byte id. If we can't find the address, we
* don ' t know if it ' s 02 or 03 , so we try both . If we ' re here , we
* failed 02. */
static struct command_result * try_other_parity ( struct command * cmd ,
const char * buf ,
const jsmntok_t * result ,
struct connect_attempt * ca )
{
struct out_req * req ;
/* Flip parity */
ca - > node_id . k [ 0 ] = SECP256K1_TAG_PUBKEY_ODD ;
2021-09-21 07:23:45 +02:00
/* Path is us -> them, so they're second entry */
if ( ! pubkey_from_node_id ( & ca - > sent - > path [ 1 ] , & ca - > node_id ) ) {
/* Should not happen!
* Pieter Wuille points out :
* y ^ 2 = x ^ 3 + 7 mod p
* negating y doesn ’ t change the left hand side
*/
return command_done_err ( cmd , LIGHTNINGD ,
" Failed: could not convert inverted pubkey? " ,
NULL ) ;
}
2021-09-21 06:05:24 +02:00
req = jsonrpc_request_start ( cmd - > plugin , cmd , " connect " , connected ,
connect_failed , ca ) ;
json_add_node_id ( req - > js , " id " , & ca - > node_id ) ;
return send_outreq ( cmd - > plugin , req ) ;
}
2021-07-01 06:28:57 +02:00
/* We can't find a route, so we're going to try to connect, then just blast it
* to them . */
static struct command_result *
connect_direct ( struct command * cmd ,
2021-10-08 00:54:42 +02:00
const struct point32 * dst ,
2021-09-21 06:05:24 +02:00
enum nodeid_parity parity ,
2021-07-01 06:28:57 +02:00
struct command_result * ( * cb ) ( struct command * command ,
const char * buf ,
const jsmntok_t * result ,
struct sent * sent ) ,
struct sent * sent )
{
struct out_req * req ;
2021-09-21 06:05:24 +02:00
struct connect_attempt * ca = tal ( cmd , struct connect_attempt ) ;
ca - > cb = cb ;
ca - > sent = sent ;
if ( parity = = nodeid_parity_unknown ) {
plugin_notify_message ( cmd , LOG_INFORM ,
" Cannot find route, trying connect to 02/03%s directly " ,
2021-10-08 00:54:42 +02:00
type_to_string ( tmpctx , struct point32 , dst ) ) ;
2021-09-21 06:05:24 +02:00
/* Try even first. */
2021-10-08 00:54:42 +02:00
node_id_from_point32 ( & ca - > node_id , dst , SECP256K1_TAG_PUBKEY_EVEN ) ;
2021-09-21 06:05:24 +02:00
} else {
plugin_notify_message ( cmd , LOG_INFORM ,
" Cannot find route, trying connect to %02x%s directly " ,
parity ,
2021-10-08 00:54:42 +02:00
type_to_string ( tmpctx , struct point32 , dst ) ) ;
node_id_from_point32 ( & ca - > node_id , dst , parity ) ;
2021-09-21 06:05:24 +02:00
}
/* Make a direct path -> dst. */
2021-09-21 07:23:45 +02:00
sent - > path = tal_arr ( sent , struct pubkey , 2 ) ;
sent - > path [ 0 ] = local_id ;
if ( ! pubkey_from_node_id ( & sent - > path [ 1 ] , & ca - > node_id ) ) {
/* Should not happen! */
return command_done_err ( cmd , LIGHTNINGD ,
" Failed: could not convert to pubkey? " ,
NULL ) ;
}
2021-07-01 06:28:57 +02:00
if ( disable_connect ) {
2021-09-21 06:05:24 +02:00
/* FIXME: This means we will fail if parity is wrong! */
2021-07-01 06:28:57 +02:00
plugin_notify_message ( cmd , LOG_UNUSUAL ,
" Cannot find route, but "
" fetchplugin-noconnect set: "
" trying direct anyway to %s " ,
2021-10-08 00:54:42 +02:00
type_to_string ( tmpctx , struct point32 ,
2021-07-01 06:28:57 +02:00
dst ) ) ;
return cb ( cmd , NULL , NULL , sent ) ;
}
2021-09-21 06:05:24 +02:00
req = jsonrpc_request_start ( cmd - > plugin , cmd , " connect " , connected ,
parity = = nodeid_parity_unknown ?
try_other_parity : connect_failed , ca ) ;
json_add_node_id ( req - > js , " id " , & ca - > node_id ) ;
2021-07-01 06:28:57 +02:00
return send_outreq ( cmd - > plugin , req ) ;
}
2020-12-16 04:18:00 +01:00
static struct command_result * invreq_done ( struct command * cmd ,
const char * buf ,
const jsmntok_t * result ,
2021-01-07 19:40:47 +01:00
struct sent * sent )
2020-12-16 04:18:00 +01:00
{
const jsmntok_t * t ;
char * fail ;
2021-09-21 06:05:24 +02:00
enum nodeid_parity parity ;
2020-12-16 04:18:00 +01:00
/* Get invoice request */
t = json_get_member ( buf , result , " bolt12 " ) ;
if ( ! t )
return command_fail ( cmd , LIGHTNINGD ,
" Missing bolt12 %.*s " ,
json_tok_full_len ( result ) ,
json_tok_full ( buf , result ) ) ;
plugin_log ( cmd - > plugin , LOG_DBG ,
" invoice_request: %.*s " ,
json_tok_full_len ( t ) ,
json_tok_full ( buf , t ) ) ;
2021-01-07 19:38:47 +01:00
sent - > inv = NULL ;
2020-12-16 04:18:00 +01:00
sent - > invreq = invrequest_decode ( sent ,
buf + t - > start ,
t - > end - t - > start ,
plugin_feature_set ( cmd - > plugin ) ,
chainparams ,
& fail ) ;
if ( ! sent - > invreq )
return command_fail ( cmd , LIGHTNINGD ,
" Invalid invoice_request %.*s: %s " ,
json_tok_full_len ( t ) ,
json_tok_full ( buf , t ) ,
fail ) ;
2021-01-07 19:41:47 +01:00
/* Now that's given us the previous base, check this is an OK time
* to request an invoice . */
if ( sent - > invreq - > recurrence_counter ) {
u64 * base ;
const jsmntok_t * pbtok ;
u64 period_idx = * sent - > invreq - > recurrence_counter ;
if ( sent - > invreq - > recurrence_start )
period_idx + = * sent - > invreq - > recurrence_start ;
2021-11-30 04:06:05 +01:00
/* BOLT-offers-recurrence #12:
2021-01-07 19:41:47 +01:00
* - if the offer contained ` recurrence_limit ` :
* - MUST NOT send an ` invoice_request ` for a period greater
* than ` max_period `
*/
if ( sent - > offer - > recurrence_limit
& & period_idx > * sent - > offer - > recurrence_limit )
return command_fail ( cmd , LIGHTNINGD ,
" Can't send invreq for period % "
PRIu64 " (limit %u) " ,
period_idx ,
* sent - > offer - > recurrence_limit ) ;
2021-11-30 04:06:05 +01:00
/* BOLT-offers-recurrence #12:
2021-01-07 19:41:47 +01:00
* - SHOULD NOT send an ` invoice_request ` for a period which has
* already passed .
*/
/* If there's no recurrence_base, we need a previous payment
* for this : fortunately createinvoicerequest does that
* lookup . */
pbtok = json_get_member ( buf , result , " previous_basetime " ) ;
if ( pbtok ) {
base = tal ( tmpctx , u64 ) ;
json_to_u64 ( buf , pbtok , base ) ;
} else if ( sent - > offer - > recurrence_base )
base = & sent - > offer - > recurrence_base - > basetime ;
else {
/* happens with *recurrence_base == 0 */
assert ( * sent - > invreq - > recurrence_counter = = 0 ) ;
base = NULL ;
}
if ( base ) {
u64 period_start , period_end , now = time_now ( ) . ts . tv_sec ;
offer_period_paywindow ( sent - > offer - > recurrence ,
sent - > offer - > recurrence_paywindow ,
sent - > offer - > recurrence_base ,
* base , period_idx ,
& period_start , & period_end ) ;
if ( now < period_start )
return command_fail ( cmd , LIGHTNINGD ,
" Too early: can't send until time % "
PRIu64 " (in % " PRIu64 " secs) " ,
period_start ,
period_start - now ) ;
if ( now > period_end )
return command_fail ( cmd , LIGHTNINGD ,
" Too late: expired time % "
PRIu64 " (% " PRIu64 " secs ago) " ,
period_end ,
now - period_end ) ;
}
}
2021-09-21 07:23:45 +02:00
sent - > path = path_to_node ( sent , cmd - > plugin ,
2021-07-01 06:28:57 +02:00
sent - > offer - > node_id ,
2021-09-21 06:05:24 +02:00
& parity ) ;
if ( ! sent - > path )
return connect_direct ( cmd , sent - > offer - > node_id , parity ,
2021-07-01 06:28:57 +02:00
sendinvreq_after_connect , sent ) ;
return sendinvreq_after_connect ( cmd , NULL , NULL , sent ) ;
2020-12-16 04:18:00 +01:00
}
2021-07-05 08:23:29 +02:00
/* If they hand us the payer secret, we sign it directly, bypassing checks
* about periods etc . */
static struct command_result *
force_payer_secret ( struct command * cmd ,
struct sent * sent ,
struct tlv_invoice_request * invreq ,
const struct secret * payer_secret )
{
struct sha256 merkle , sha ;
2021-09-21 06:05:24 +02:00
enum nodeid_parity parity ;
2021-07-05 08:23:29 +02:00
secp256k1_keypair kp ;
u8 * msg ;
const u8 * p ;
size_t len ;
if ( secp256k1_keypair_create ( secp256k1_ctx , & kp , payer_secret - > data ) ! = 1 )
return command_fail ( cmd , LIGHTNINGD , " Bad payer_secret " ) ;
2021-10-08 00:54:42 +02:00
invreq - > payer_key = tal ( invreq , struct point32 ) ;
2021-07-05 08:23:29 +02:00
/* Docs say this only happens if arguments are invalid! */
if ( secp256k1_keypair_xonly_pub ( secp256k1_ctx ,
& invreq - > payer_key - > pubkey , NULL ,
& kp ) ! = 1 )
plugin_err ( cmd - > plugin ,
" secp256k1_keypair_pub failed on %s? " ,
type_to_string ( tmpctx , struct secret , payer_secret ) ) ;
/* Linearize populates ->fields */
msg = tal_arr ( tmpctx , u8 , 0 ) ;
2022-03-23 00:31:14 +01:00
towire_tlv_invoice_request ( & msg , invreq ) ;
2021-07-05 08:23:29 +02:00
p = msg ;
len = tal_bytelen ( msg ) ;
2022-03-23 00:31:14 +01:00
sent - > invreq = fromwire_tlv_invoice_request ( cmd , & p , & len ) ;
if ( ! sent - > invreq )
2021-07-05 08:23:29 +02:00
plugin_err ( cmd - > plugin ,
" Could not remarshall invreq %s " , tal_hex ( tmpctx , msg ) ) ;
merkle_tlv ( sent - > invreq - > fields , & merkle ) ;
2022-03-22 09:50:13 +01:00
if ( deprecated_apis )
sighash_from_merkle ( " invoice_request " , " payer_signature " , & merkle , & sha ) ;
else
sighash_from_merkle ( " invoice_request " , " signature " , & merkle , & sha ) ;
2021-07-05 08:23:29 +02:00
2022-03-22 09:50:13 +01:00
sent - > invreq - > signature = tal ( invreq , struct bip340sig ) ;
2022-04-07 01:54:52 +02:00
if ( ! secp256k1_schnorrsig_sign32 ( secp256k1_ctx ,
2022-03-22 09:50:13 +01:00
sent - > invreq - > signature - > u8 ,
2021-07-05 08:23:29 +02:00
sha . u . u8 ,
& kp ,
2022-04-07 01:54:52 +02:00
NULL ) ) {
2021-07-05 08:23:29 +02:00
return command_fail ( cmd , LIGHTNINGD ,
" Failed to sign with payer_secret " ) ;
}
2021-09-21 07:23:45 +02:00
sent - > path = path_to_node ( sent , cmd - > plugin ,
2021-07-05 08:23:29 +02:00
sent - > offer - > node_id ,
2021-09-21 06:05:24 +02:00
& parity ) ;
if ( ! sent - > path )
return connect_direct ( cmd , sent - > offer - > node_id , parity ,
2021-07-05 08:23:29 +02:00
sendinvreq_after_connect , sent ) ;
return sendinvreq_after_connect ( cmd , NULL , NULL , sent ) ;
}
2020-12-16 04:18:00 +01:00
/* Fetches an invoice for this offer, and makes sure it corresponds. */
static struct command_result * json_fetchinvoice ( struct command * cmd ,
const char * buffer ,
const jsmntok_t * params )
{
struct amount_msat * msat ;
2021-07-02 02:11:35 +02:00
const char * rec_label , * payer_note ;
2020-12-16 04:18:00 +01:00
struct out_req * req ;
struct tlv_invoice_request * invreq ;
2021-01-07 19:40:47 +01:00
struct sent * sent = tal ( cmd , struct sent ) ;
2021-07-05 08:23:29 +02:00
struct secret * payer_secret = NULL ;
2021-01-07 19:40:47 +01:00
u32 * timeout ;
2020-12-16 04:18:00 +01:00
2021-01-07 19:40:47 +01:00
invreq = tlv_invoice_request_new ( sent ) ;
2020-12-16 04:18:00 +01:00
if ( ! param ( cmd , buffer , params ,
2021-01-07 19:40:47 +01:00
p_req ( " offer " , param_offer , & sent - > offer ) ,
lightningd: change `msatoshi` args to `amount_msat`.
This is consistent with our output changes, and increases consistency.
It also keeps future sanity checks happy, that we only use JSON msat
helpers with '_msat' fields.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Changelog-Changed: JSON-RPC: `invoice`, `sendonion`, `sendpay`, `pay`, `keysend`, `fetchinvoice`, `sendinvoice`: `msatoshi` argument is now called `amount_msat` to match other fields.
Changelog-Deprecated: JSON-RPC: `invoice`, `sendonion`, `sendpay`, `pay`, `keysend`, `fetchinvoice`, `sendinvoice` `msatoshi` (use `amount_msat`)
2022-06-19 09:20:11 +02:00
p_opt ( " amount_msat|msatoshi " , param_msat , & msat ) ,
2020-12-16 04:18:00 +01:00
p_opt ( " quantity " , param_u64 , & invreq - > quantity ) ,
p_opt ( " recurrence_counter " , param_number ,
& invreq - > recurrence_counter ) ,
p_opt ( " recurrence_start " , param_number ,
& invreq - > recurrence_start ) ,
p_opt ( " recurrence_label " , param_string , & rec_label ) ,
2021-01-07 19:40:47 +01:00
p_opt_def ( " timeout " , param_number , & timeout , 60 ) ,
2021-07-02 02:11:35 +02:00
p_opt ( " payer_note " , param_string , & payer_note ) ,
2021-07-05 08:23:29 +02:00
# if DEVELOPER
p_opt ( " payer_secret " , param_secret , & payer_secret ) ,
# endif
2020-12-16 04:18:00 +01:00
NULL ) )
return command_param_failed ( ) ;
2021-01-07 19:40:47 +01:00
sent - > wait_timeout = * timeout ;
2020-12-16 04:18:00 +01:00
/* BOLT-offers #12:
2021-07-05 13:27:39 +02:00
* - MUST set ` offer_id ` to the Merkle root of the offer as described
2020-12-16 04:18:00 +01:00
* in [ Signature Calculation ] ( # signature - calculation ) .
*/
invreq - > offer_id = tal ( invreq , struct sha256 ) ;
2021-01-07 19:40:47 +01:00
merkle_tlv ( sent - > offer - > fields , invreq - > offer_id ) ;
2020-12-16 04:18:00 +01:00
/* Check if they are trying to send us money. */
2021-01-07 19:40:47 +01:00
if ( sent - > offer - > send_invoice )
2020-12-16 04:18:00 +01:00
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" Offer wants an invoice, not invoice_request " ) ;
/* BOLT-offers #12:
* - SHOULD not respond to an offer if the current time is after
* ` absolute_expiry ` .
*/
2021-01-07 19:40:47 +01:00
if ( sent - > offer - > absolute_expiry
& & time_now ( ) . ts . tv_sec > * sent - > offer - > absolute_expiry )
2020-12-16 04:18:00 +01:00
return command_fail ( cmd , OFFER_EXPIRED , " Offer expired " ) ;
2021-11-30 04:06:05 +01:00
/* BOLT-offers-recurrence #12:
2020-12-16 04:18:00 +01:00
* - if the offer did not specify ` amount ` :
* - MUST specify ` amount ` . ` msat ` in multiples of the minimum
2021-11-30 04:06:05 +01:00
* lightning - payable unit ( e . g . milli - satoshis for bitcoin ) for
* ` chain ` ( or for bitcoin , if there is no ` chain ` ) .
2020-12-16 04:18:00 +01:00
* - otherwise :
2021-01-09 05:25:46 +01:00
* - MAY omit ` amount ` .
* - if it sets ` amount ` :
* - MUST specify ` amount ` . ` msat ` as greater or equal to amount
* expected by the offer ( before any proportional period amount ) .
2020-12-16 04:18:00 +01:00
*/
2021-01-07 19:40:47 +01:00
if ( sent - > offer - > amount ) {
2021-01-09 05:25:46 +01:00
/* FIXME: Check after quantity? */
if ( msat ) {
invreq - > amount = tal_dup ( invreq , u64 ,
& msat - > millisatoshis ) ; /* Raw: tu64 */
}
2020-12-16 04:18:00 +01:00
} else {
if ( ! msat )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" msatoshi parameter required " ) ;
invreq - > amount = tal_dup ( invreq , u64 ,
& msat - > millisatoshis ) ; /* Raw: tu64 */
}
/* BOLT-offers #12:
* - if the offer had a ` quantity_min ` or ` quantity_max ` field :
* - MUST set ` quantity `
* - MUST set it within that ( inclusive ) range .
* - otherwise :
* - MUST NOT set ` quantity `
*/
2021-01-07 19:40:47 +01:00
if ( sent - > offer - > quantity_min | | sent - > offer - > quantity_max ) {
2020-12-16 04:18:00 +01:00
if ( ! invreq - > quantity )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" quantity parameter required " ) ;
2021-01-07 19:40:47 +01:00
if ( sent - > offer - > quantity_min
& & * invreq - > quantity < * sent - > offer - > quantity_min )
2020-12-16 04:18:00 +01:00
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" quantity must be >= % " PRIu64 ,
2021-01-07 19:40:47 +01:00
* sent - > offer - > quantity_min ) ;
if ( sent - > offer - > quantity_max
& & * invreq - > quantity > * sent - > offer - > quantity_max )
2020-12-16 04:18:00 +01:00
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" quantity must be <= % " PRIu64 ,
2021-01-07 19:40:47 +01:00
* sent - > offer - > quantity_max ) ;
2020-12-16 04:18:00 +01:00
} else {
if ( invreq - > quantity )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" quantity parameter unnecessary " ) ;
}
2021-11-30 04:06:05 +01:00
/* BOLT-offers-recurrence #12:
2020-12-16 04:18:00 +01:00
* - if the offer contained ` recurrence ` :
*/
2021-01-07 19:40:47 +01:00
if ( sent - > offer - > recurrence ) {
2021-11-30 04:06:05 +01:00
/* BOLT-offers-recurrence #12:
2020-12-16 04:18:00 +01:00
* - for the initial request :
* . . .
* - MUST set ` recurrence_counter ` ` counter ` to 0.
*/
2021-11-30 04:06:05 +01:00
/* BOLT-offers-recurrence #12:
2020-12-16 04:18:00 +01:00
* - for any successive requests :
* . . .
* - MUST set ` recurrence_counter ` ` counter ` to one greater
* than the highest - paid invoice .
*/
if ( ! invreq - > recurrence_counter )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" needs recurrence_counter " ) ;
2021-11-30 04:06:05 +01:00
/* BOLT-offers-recurrence #12:
2020-12-16 04:18:00 +01:00
* - if the offer contained ` recurrence_base ` with
* ` start_any_period ` non - zero :
* - MUST include ` recurrence_start `
* . . .
* - otherwise :
* - MUST NOT include ` recurrence_start `
*/
2021-01-07 19:40:47 +01:00
if ( sent - > offer - > recurrence_base
& & sent - > offer - > recurrence_base - > start_any_period ) {
2020-12-16 04:18:00 +01:00
if ( ! invreq - > recurrence_start )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" needs recurrence_start " ) ;
} else {
if ( invreq - > recurrence_start )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" unnecessary recurrence_start " ) ;
}
/* recurrence_label uniquely identifies this series of
2021-07-05 08:23:29 +02:00
* payments ( unless they supply secret themselves ) ! */
if ( ! rec_label & & ! payer_secret )
2020-12-16 04:18:00 +01:00
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" needs recurrence_label " ) ;
} else {
2021-11-30 04:06:05 +01:00
/* BOLT-offers-recurrence #12:
2020-12-16 04:18:00 +01:00
* - otherwise :
* - MUST NOT set ` recurrence_counter ` .
* - MUST NOT set ` recurrence_start `
*/
if ( invreq - > recurrence_counter )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" unnecessary recurrence_counter " ) ;
if ( invreq - > recurrence_start )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" unnecessary recurrence_start " ) ;
}
/* BOLT-offers #12:
*
* - if the chain for the invoice is not solely bitcoin :
* - MUST specify ` chains ` the offer is valid for .
* - otherwise :
* - the bitcoin chain is implied as the first and only entry .
*/
if ( ! streq ( chainparams - > network_name , " bitcoin " ) ) {
2021-10-08 05:27:29 +02:00
invreq - > chain = tal_dup ( invreq , struct bitcoin_blkid ,
& chainparams - > genesis_blockhash ) ;
2020-12-16 04:18:00 +01:00
}
invreq - > features
= plugin_feature_set ( cmd - > plugin ) - > bits [ BOLT11_FEATURE ] ;
2021-07-02 02:11:35 +02:00
/* invreq->payer_note is not a nul-terminated string! */
if ( payer_note )
invreq - > payer_note = tal_dup_arr ( invreq , utf8 ,
payer_note , strlen ( payer_note ) ,
0 ) ;
2021-07-05 08:23:29 +02:00
/* They can provide a secret, and we don't assume it's our job
* to pay . */
if ( payer_secret )
return force_payer_secret ( cmd , sent , invreq , payer_secret ) ;
2020-12-16 04:18:00 +01:00
/* Make the invoice request (fills in payer_key and payer_info) */
req = jsonrpc_request_start ( cmd - > plugin , cmd , " createinvoicerequest " ,
& invreq_done ,
& forward_error ,
2021-01-07 19:40:47 +01:00
sent ) ;
2020-12-16 04:18:00 +01:00
json_add_string ( req - > js , " bolt12 " , invrequest_encode ( tmpctx , invreq ) ) ;
if ( rec_label )
json_add_string ( req - > js , " recurrence_label " , rec_label ) ;
return send_outreq ( cmd - > plugin , req ) ;
}
2021-01-07 19:39:47 +01:00
/* FIXME: Using a hook here is not ideal: technically it doesn't mean
* it ' s actually hit the db ! But using waitinvoice is also suboptimal
* because we don ' t have libplugin infra to cancel a pending req ( and I
* want to rewrite our wait * API anyway ) */
static struct command_result * invoice_payment ( struct command * cmd ,
const char * buf ,
const jsmntok_t * params )
{
struct sent * i ;
const jsmntok_t * ptok , * preimagetok , * msattok ;
struct preimage preimage ;
struct amount_msat msat ;
ptok = json_get_member ( buf , params , " payment " ) ;
preimagetok = json_get_member ( buf , ptok , " preimage " ) ;
msattok = json_get_member ( buf , ptok , " msat " ) ;
if ( ! preimagetok | | ! msattok )
plugin_err ( cmd - > plugin ,
" Invalid invoice_payment %.*s " ,
json_tok_full_len ( params ) ,
json_tok_full ( buf , params ) ) ;
hex_decode ( buf + preimagetok - > start ,
preimagetok - > end - preimagetok - > start ,
& preimage , sizeof ( preimage ) ) ;
json_to_msat ( buf , msattok , & msat ) ;
list_for_each ( & sent_list , i , list ) {
2021-01-07 19:50:47 +01:00
struct out_req * req ;
2021-01-07 19:39:47 +01:00
if ( ! i - > inv )
continue ;
if ( ! preimage_eq ( & preimage , & i - > inv_preimage ) )
continue ;
2021-01-07 19:50:47 +01:00
/* It was paid! Success. Return as per waitinvoice. */
req = jsonrpc_request_start ( cmd - > plugin , i - > cmd , " waitinvoice " ,
& forward_result ,
& forward_error ,
i ) ;
json_add_escaped_string ( req - > js , " label " , i - > inv_label ) ;
discard_result ( send_outreq ( cmd - > plugin , req ) ) ;
2021-01-07 19:39:47 +01:00
break ;
}
return command_hook_success ( cmd ) ;
}
2021-07-01 06:28:57 +02:00
/* We've connected (if we tried), so send the invoice. */
static struct command_result *
sendinvoice_after_connect ( struct command * cmd ,
const char * buf UNUSED ,
const jsmntok_t * result UNUSED ,
struct sent * sent )
{
2022-03-28 01:10:54 +02:00
u8 * rawinv = tal_arr ( tmpctx , u8 , 0 ) ;
towire_tlv_invoice ( & rawinv , sent - > inv ) ;
return send_message ( cmd , sent , " invoice " , rawinv , prepare_inv_timeout ) ;
2021-07-01 06:28:57 +02:00
}
2021-01-07 19:38:47 +01:00
static struct command_result * createinvoice_done ( struct command * cmd ,
const char * buf ,
const jsmntok_t * result ,
struct sent * sent )
{
const jsmntok_t * invtok = json_get_member ( buf , result , " bolt12 " ) ;
char * fail ;
2021-09-21 06:05:24 +02:00
enum nodeid_parity parity ;
2021-01-07 19:38:47 +01:00
/* Replace invoice with signed one */
tal_free ( sent - > inv ) ;
sent - > inv = invoice_decode ( sent ,
buf + invtok - > start ,
invtok - > end - invtok - > start ,
plugin_feature_set ( cmd - > plugin ) ,
chainparams ,
& fail ) ;
if ( ! sent - > inv ) {
plugin_log ( cmd - > plugin , LOG_BROKEN ,
" Bad createinvoice %.*s: %s " ,
json_tok_full_len ( invtok ) ,
json_tok_full ( buf , invtok ) ,
fail ) ;
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" Bad createinvoice response %s " , fail ) ;
2020-12-16 04:18:00 +01:00
}
2021-01-07 19:38:47 +01:00
2021-09-21 07:23:45 +02:00
sent - > path = path_to_node ( sent , cmd - > plugin ,
2021-07-01 06:28:57 +02:00
sent - > offer - > node_id ,
2021-09-21 06:05:24 +02:00
& parity ) ;
if ( ! sent - > path )
return connect_direct ( cmd , sent - > offer - > node_id , parity ,
2021-07-01 06:28:57 +02:00
sendinvoice_after_connect , sent ) ;
return sendinvoice_after_connect ( cmd , NULL , NULL , sent ) ;
2021-01-07 19:38:47 +01:00
}
static struct command_result * sign_invoice ( struct command * cmd ,
struct sent * sent )
{
struct out_req * req ;
/* Get invoice signature and put in db so we can receive payment */
req = jsonrpc_request_start ( cmd - > plugin , cmd , " createinvoice " ,
& createinvoice_done ,
& forward_error ,
sent ) ;
json_add_string ( req - > js , " invstring " , invoice_encode ( tmpctx , sent - > inv ) ) ;
json_add_preimage ( req - > js , " preimage " , & sent - > inv_preimage ) ;
json_add_escaped_string ( req - > js , " label " , sent - > inv_label ) ;
return send_outreq ( cmd - > plugin , req ) ;
}
static bool json_to_bip340sig ( const char * buffer , const jsmntok_t * tok ,
struct bip340sig * sig )
{
return hex_decode ( buffer + tok - > start , tok - > end - tok - > start ,
sig - > u8 , sizeof ( sig - > u8 ) ) ;
}
static struct command_result * payersign_done ( struct command * cmd ,
const char * buf ,
const jsmntok_t * result ,
struct sent * sent )
{
const jsmntok_t * sig ;
sent - > inv - > refund_signature = tal ( sent - > inv , struct bip340sig ) ;
sig = json_get_member ( buf , result , " signature " ) ;
json_to_bip340sig ( buf , sig , sent - > inv - > refund_signature ) ;
return sign_invoice ( cmd , sent ) ;
}
/* They're offering a refund, so we need to sign with same key as used
* in initial payment . */
static struct command_result * listsendpays_done ( struct command * cmd ,
const char * buf ,
const jsmntok_t * result ,
struct sent * sent )
{
const jsmntok_t * t , * arr = json_get_member ( buf , result , " payments " ) ;
size_t i ;
const u8 * public_tweak = NULL , * p ;
u8 * msg ;
size_t len ;
struct sha256 merkle ;
struct out_req * req ;
/* Linearize populates ->fields */
msg = tal_arr ( tmpctx , u8 , 0 ) ;
2022-03-23 00:31:14 +01:00
towire_tlv_invoice ( & msg , sent - > inv ) ;
2021-01-07 19:38:47 +01:00
p = msg ;
len = tal_bytelen ( msg ) ;
2022-03-23 00:31:14 +01:00
sent - > inv = fromwire_tlv_invoice ( cmd , & p , & len ) ;
if ( ! sent - > inv )
2021-01-07 19:38:47 +01:00
plugin_err ( cmd - > plugin ,
" Could not remarshall %s " , tal_hex ( tmpctx , msg ) ) ;
merkle_tlv ( sent - > inv - > fields , & merkle ) ;
json_for_each_arr ( i , t , arr ) {
const jsmntok_t * b12tok ;
struct tlv_invoice * inv ;
char * fail ;
b12tok = json_get_member ( buf , t , " bolt12 " ) ;
if ( ! b12tok ) {
/* This could happen if they try to refund a bolt11 */
plugin_log ( cmd - > plugin , LOG_UNUSUAL ,
" Not bolt12 string in %.*s? " ,
json_tok_full_len ( t ) ,
json_tok_full ( buf , t ) ) ;
continue ;
}
inv = invoice_decode ( tmpctx , buf + b12tok - > start ,
b12tok - > end - b12tok - > start ,
plugin_feature_set ( cmd - > plugin ) ,
chainparams ,
& fail ) ;
if ( ! inv ) {
plugin_log ( cmd - > plugin , LOG_BROKEN ,
" Bad bolt12 string in %.*s? " ,
json_tok_full_len ( t ) ,
json_tok_full ( buf , t ) ) ;
continue ;
}
public_tweak = inv - > payer_info ;
break ;
}
if ( ! public_tweak )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" Cannot find invoice %s for refund " ,
type_to_string ( tmpctx , struct sha256 ,
sent - > offer - > refund_for ) ) ;
/* BOLT-offers #12:
* - MUST set ` refund_signature ` to the signature of the
* ` refunded_payment_hash ` using prefix ` refund_signature ` and the
* ` payer_key ` from the to - be - refunded invoice .
*/
req = jsonrpc_request_start ( cmd - > plugin , cmd , " payersign " ,
& payersign_done ,
& forward_error ,
sent ) ;
json_add_string ( req - > js , " messagename " , " invoice " ) ;
json_add_string ( req - > js , " fieldname " , " refund_signature " ) ;
json_add_sha256 ( req - > js , " merkle " , & merkle ) ;
json_add_hex_talarr ( req - > js , " tweak " , public_tweak ) ;
return send_outreq ( cmd - > plugin , req ) ;
}
static struct command_result * json_sendinvoice ( struct command * cmd ,
const char * buffer ,
const jsmntok_t * params )
{
struct amount_msat * msat ;
struct out_req * req ;
2021-01-07 19:53:47 +01:00
u32 * timeout ;
2021-01-07 19:38:47 +01:00
struct sent * sent = tal ( cmd , struct sent ) ;
sent - > inv = tlv_invoice_new ( cmd ) ;
sent - > invreq = NULL ;
sent - > cmd = cmd ;
/* FIXME: Support recurring send_invoice offers? */
if ( ! param ( cmd , buffer , params ,
p_req ( " offer " , param_offer , & sent - > offer ) ,
p_req ( " label " , param_label , & sent - > inv_label ) ,
lightningd: change `msatoshi` args to `amount_msat`.
This is consistent with our output changes, and increases consistency.
It also keeps future sanity checks happy, that we only use JSON msat
helpers with '_msat' fields.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Changelog-Changed: JSON-RPC: `invoice`, `sendonion`, `sendpay`, `pay`, `keysend`, `fetchinvoice`, `sendinvoice`: `msatoshi` argument is now called `amount_msat` to match other fields.
Changelog-Deprecated: JSON-RPC: `invoice`, `sendonion`, `sendpay`, `pay`, `keysend`, `fetchinvoice`, `sendinvoice` `msatoshi` (use `amount_msat`)
2022-06-19 09:20:11 +02:00
p_opt ( " amount_msat|msatoshi " , param_msat , & msat ) ,
2021-01-07 19:39:47 +01:00
p_opt_def ( " timeout " , param_number , & timeout , 90 ) ,
2021-01-07 19:38:47 +01:00
p_opt ( " quantity " , param_u64 , & sent - > inv - > quantity ) ,
NULL ) )
return command_param_failed ( ) ;
2021-01-07 19:39:47 +01:00
/* This is how long we'll wait for a reply for. */
2021-01-07 19:40:47 +01:00
sent - > wait_timeout = * timeout ;
2021-01-07 19:39:47 +01:00
2021-01-07 19:38:47 +01:00
/* Check they are really trying to send us money. */
if ( ! sent - > offer - > send_invoice )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" Offer wants an invoice_request, not invoice " ) ;
/* If they don't tell us how much, base it on offer. */
if ( ! msat ) {
if ( sent - > offer - > currency )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" Offer in different currency: need amount " ) ;
if ( ! sent - > offer - > amount )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" Offer did not specify: need amount " ) ;
sent - > inv - > amount = tal_dup ( sent - > inv , u64 , sent - > offer - > amount ) ;
if ( sent - > inv - > quantity )
* sent - > inv - > amount * = * sent - > inv - > quantity ;
} else
sent - > inv - > amount = tal_dup ( sent - > inv , u64 ,
& msat - > millisatoshis ) ; /* Raw: tlv */
/* FIXME: Support blinded paths, in which case use fake nodeid */
/* BOLT-offers #12:
* - otherwise ( responding to a ` send_invoice ` offer ) :
* - MUST set ` node_id ` to the id of the node to send payment to .
* - MUST set ` description ` the same as the offer .
*/
2021-10-08 00:54:42 +02:00
sent - > inv - > node_id = tal ( sent - > inv , struct point32 ) ;
2021-09-21 07:23:45 +02:00
/* This only fails if pubkey is invalid. */
if ( ! secp256k1_xonly_pubkey_from_pubkey ( secp256k1_ctx ,
& sent - > inv - > node_id - > pubkey ,
NULL ,
& local_id . pubkey ) )
abort ( ) ;
2021-01-07 19:38:47 +01:00
sent - > inv - > description
= tal_dup_talarr ( sent - > inv , char , sent - > offer - > description ) ;
/* BOLT-offers #12:
* - MUST set ( or not set ) ` send_invoice ` the same as the offer .
*/
sent - > inv - > send_invoice = tal ( sent - > inv , struct tlv_invoice_send_invoice ) ;
/* BOLT-offers #12:
* - MUST set ` offer_id ` to the id of the offer .
*/
sent - > inv - > offer_id = tal ( sent - > inv , struct sha256 ) ;
merkle_tlv ( sent - > offer - > fields , sent - > inv - > offer_id ) ;
/* BOLT-offers #12:
* - SHOULD not respond to an offer if the current time is after
* ` absolute_expiry ` .
*/
if ( sent - > offer - > absolute_expiry
& & time_now ( ) . ts . tv_sec > * sent - > offer - > absolute_expiry )
return command_fail ( cmd , OFFER_EXPIRED , " Offer expired " ) ;
/* BOLT-offers #12:
* - otherwise ( responding to a ` send_invoice ` offer ) :
* . . .
* - if the offer had a ` quantity_min ` or ` quantity_max ` field :
* - MUST set ` quantity `
* - MUST set it within that ( inclusive ) range .
* - otherwise :
* - MUST NOT set ` quantity `
*/
if ( sent - > offer - > quantity_min | | sent - > offer - > quantity_max ) {
if ( ! sent - > inv - > quantity )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" quantity parameter required " ) ;
if ( sent - > offer - > quantity_min
& & * sent - > inv - > quantity < * sent - > offer - > quantity_min )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" quantity must be >= % " PRIu64 ,
* sent - > offer - > quantity_min ) ;
if ( sent - > offer - > quantity_max
& & * sent - > inv - > quantity > * sent - > offer - > quantity_max )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" quantity must be <= % " PRIu64 ,
* sent - > offer - > quantity_max ) ;
} else {
if ( sent - > inv - > quantity )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" quantity parameter unnecessary " ) ;
}
2021-01-07 19:39:47 +01:00
/* BOLT-offers #12:
2021-07-21 03:31:39 +02:00
* - MUST set ` created_at ` to the number of seconds since Midnight 1
* January 1970 , UTC when the offer was created .
2021-01-07 19:39:47 +01:00
*/
2021-07-21 03:31:39 +02:00
sent - > inv - > created_at = tal ( sent - > inv , u64 ) ;
* sent - > inv - > created_at = time_now ( ) . ts . tv_sec ;
2021-01-07 19:39:47 +01:00
/* BOLT-offers #12:
* - if the expiry for accepting payment is not 7200 seconds after
2021-07-21 03:31:39 +02:00
* ` created_at ` :
* - MUST set ` relative_expiry ` ` seconds_from_creation ` to the number
* of seconds after ` created_at ` that payment of this invoice should
2021-01-07 19:39:47 +01:00
* not be attempted .
*/
2021-01-07 19:53:47 +01:00
if ( sent - > wait_timeout ! = 7200 ) {
2021-01-07 19:39:47 +01:00
sent - > inv - > relative_expiry = tal ( sent - > inv , u32 ) ;
2021-01-07 19:53:47 +01:00
* sent - > inv - > relative_expiry = sent - > wait_timeout ;
2021-01-07 19:39:47 +01:00
}
2021-01-07 19:38:47 +01:00
/* BOLT-offers #12:
* - MUST set ` payer_key ` to the ` node_id ` of the offer .
*/
sent - > inv - > payer_key = sent - > offer - > node_id ;
2021-11-30 04:06:05 +01:00
/* FIXME: recurrence? */
2021-01-07 19:38:47 +01:00
if ( sent - > offer - > recurrence )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" FIXME: handle recurring send_invoice offer! " ) ;
/* BOLT-offers #12:
*
* - if the chain for the invoice is not solely bitcoin :
* - MUST specify ` chains ` the offer is valid for .
* - otherwise :
* - the bitcoin chain is implied as the first and only entry .
*/
if ( ! streq ( chainparams - > network_name , " bitcoin " ) ) {
2021-10-08 05:27:29 +02:00
sent - > inv - > chain = tal_dup ( sent - > inv , struct bitcoin_blkid ,
& chainparams - > genesis_blockhash ) ;
2021-01-07 19:38:47 +01:00
}
sent - > inv - > features
= plugin_feature_set ( cmd - > plugin ) - > bits [ BOLT11_FEATURE ] ;
randombytes_buf ( & sent - > inv_preimage , sizeof ( sent - > inv_preimage ) ) ;
sent - > inv - > payment_hash = tal ( sent - > inv , struct sha256 ) ;
sha256 ( sent - > inv - > payment_hash ,
& sent - > inv_preimage , sizeof ( sent - > inv_preimage ) ) ;
/* BOLT-offers #12:
* - MUST set ( or not set ) ` refund_for ` exactly as the offer did .
* - if it sets ` refund_for ` :
* - MUST set ` refund_signature ` to the signature of the
* ` refunded_payment_hash ` using prefix ` refund_signature ` and
* the ` payer_key ` from the to - be - refunded invoice .
* - otherwise :
* - MUST NOT set ` refund_signature `
*/
if ( sent - > offer - > refund_for ) {
sent - > inv - > refund_for = sent - > offer - > refund_for ;
/* Find original payment invoice */
req = jsonrpc_request_start ( cmd - > plugin , cmd , " listsendpays " ,
& listsendpays_done ,
& forward_error ,
sent ) ;
json_add_sha256 ( req - > js , " payment_hash " ,
sent - > offer - > refund_for ) ;
return send_outreq ( cmd - > plugin , req ) ;
}
return sign_invoice ( cmd , sent ) ;
}
2021-08-19 06:53:04 +02:00
# if DEVELOPER
static struct command_result * param_invreq ( struct command * cmd ,
const char * name ,
const char * buffer ,
const jsmntok_t * tok ,
struct tlv_invoice_request * * invreq )
{
char * fail ;
* invreq = invrequest_decode ( cmd , buffer + tok - > start , tok - > end - tok - > start ,
plugin_feature_set ( cmd - > plugin ) , chainparams ,
& fail ) ;
if ( ! * invreq )
return command_fail_badparam ( cmd , name , buffer , tok ,
tal_fmt ( cmd ,
" Unparsable invreq: %s " ,
fail ) ) ;
return NULL ;
}
static struct command_result * json_rawrequest ( struct command * cmd ,
const char * buffer ,
const jsmntok_t * params )
{
struct sent * sent = tal ( cmd , struct sent ) ;
u32 * timeout ;
struct node_id * node_id ;
2021-10-08 00:54:42 +02:00
struct point32 node_id32 ;
2021-09-21 06:05:24 +02:00
enum nodeid_parity parity ;
2021-08-19 06:53:04 +02:00
if ( ! param ( cmd , buffer , params ,
p_req ( " invreq " , param_invreq , & sent - > invreq ) ,
p_req ( " nodeid " , param_node_id , & node_id ) ,
p_opt_def ( " timeout " , param_number , & timeout , 60 ) ,
NULL ) )
return command_param_failed ( ) ;
/* Skip over 02/03 in node_id */
if ( ! secp256k1_xonly_pubkey_parse ( secp256k1_ctx ,
& node_id32 . pubkey ,
node_id - > k + 1 ) )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" Invalid nodeid " ) ;
/* This is how long we'll wait for a reply for. */
sent - > wait_timeout = * timeout ;
sent - > cmd = cmd ;
sent - > offer = NULL ;
2021-09-21 07:23:45 +02:00
sent - > path = path_to_node ( sent , cmd - > plugin ,
2021-09-21 06:05:24 +02:00
& node_id32 ,
& parity ) ;
if ( ! sent - > path ) {
/* We *do* know parity: they gave it to us! */
parity = node_id - > k [ 0 ] ;
return connect_direct ( cmd , & node_id32 , parity ,
2021-08-19 06:53:04 +02:00
sendinvreq_after_connect , sent ) ;
2021-09-21 06:05:24 +02:00
}
2021-08-19 06:53:04 +02:00
return sendinvreq_after_connect ( cmd , NULL , NULL , sent ) ;
}
# endif /* DEVELOPER */
2021-01-07 19:38:47 +01:00
static const struct plugin_command commands [ ] = {
{
" fetchinvoice " ,
" payment " ,
" Request remote node for an invoice for this {offer}, with {amount}, {quanitity}, {recurrence_counter}, {recurrence_start} and {recurrence_label} iff required. " ,
NULL ,
json_fetchinvoice ,
} ,
{
" sendinvoice " ,
" payment " ,
" Request remote node for to pay this send_invoice {offer}, with {amount}, {quanitity}, {recurrence_counter}, {recurrence_start} and {recurrence_label} iff required. " ,
NULL ,
json_sendinvoice ,
} ,
2021-08-19 06:53:04 +02:00
# if DEVELOPER
{
" dev-rawrequest " ,
" util " ,
" Send {invreq} to {nodeid}, wait {timeout} (60 seconds by default) " ,
NULL ,
json_rawrequest ,
} ,
# endif /* DEVELOPER */
2020-12-16 04:18:00 +01:00
} ;
2021-01-13 04:00:24 +01:00
static const char * init ( struct plugin * p , const char * buf UNUSED ,
const jsmntok_t * config UNUSED )
2020-12-16 04:18:00 +01:00
{
2021-01-13 09:58:38 +01:00
bool exp_offers ;
2021-01-06 06:41:20 +01:00
rpc_scan ( p , " getinfo " ,
take ( json_out_obj ( NULL , NULL , NULL ) ) ,
2021-09-21 07:23:45 +02:00
" {id:%} " , JSON_SCAN ( json_to_pubkey , & local_id ) ) ;
2021-01-13 04:00:24 +01:00
2021-01-13 09:58:38 +01:00
rpc_scan ( p , " listconfigs " ,
take ( json_out_obj ( NULL , " config " , " experimental-offers " ) ) ,
" {experimental-offers:%} " ,
JSON_SCAN ( json_to_bool , & exp_offers ) ) ;
if ( ! exp_offers )
return " offers not enabled in config " ;
2021-01-13 04:00:24 +01:00
return NULL ;
2020-12-16 04:18:00 +01:00
}
2020-12-16 04:18:20 +01:00
static const struct plugin_hook hooks [ ] = {
2021-09-30 23:19:36 +02:00
{
" onion_message_ourpath " ,
recv_modern_onion_message
2020-12-16 04:18:20 +01:00
} ,
2021-01-07 19:39:47 +01:00
{
" invoice_payment " ,
invoice_payment ,
} ,
2020-12-16 04:18:20 +01:00
} ;
2020-12-16 04:18:00 +01:00
int main ( int argc , char * argv [ ] )
{
setup_locale ( ) ;
plugin_main ( argv , init , PLUGIN_RESTARTABLE , true , NULL ,
commands , ARRAY_SIZE ( commands ) ,
/* No notifications */
NULL , 0 ,
2020-12-16 04:18:20 +01:00
hooks , ARRAY_SIZE ( hooks ) ,
2021-04-28 17:28:27 +02:00
NULL , 0 ,
2021-07-01 06:28:57 +02:00
plugin_option ( " fetchinvoice-noconnect " , " flag " ,
" Don't try to connect directly to fetch an invoice. " ,
flag_option , & disable_connect ) ,
2020-12-16 04:18:00 +01:00
NULL ) ;
}