2020-12-16 04:13:23 +01:00
/* This plugin covers both sending and receiving offers */
2021-12-04 12:23:56 +01:00
# include "config.h"
2021-01-07 19:48:47 +01:00
# include <bitcoin/chainparams.h>
2020-12-16 04:13:23 +01:00
# include <ccan/array_size/array_size.h>
2021-09-30 23:19:37 +02:00
# include <ccan/cast/cast.h>
2022-07-16 15:18:27 +02:00
# include <ccan/rune/rune.h>
2021-01-07 19:48:47 +01:00
# include <ccan/tal/str/str.h>
# include <common/bech32.h>
2023-10-26 05:12:13 +02:00
# include <common/bech32_util.h>
2021-01-07 19:48:47 +01:00
# include <common/bolt11.h>
# include <common/bolt11_json.h>
# include <common/bolt12_merkle.h>
2024-05-09 06:42:38 +02:00
# include <common/gossmap.h>
2022-11-09 03:32:00 +01:00
# include <common/invoice_path_id.h>
2021-01-07 19:48:47 +01:00
# include <common/iso4217.h>
2024-05-09 05:35:15 +02:00
# include <common/json_blinded_path.h>
2022-07-04 05:49:38 +02:00
# include <common/json_param.h>
2021-01-07 19:35:47 +01:00
# include <common/json_stream.h>
2024-05-09 06:42:38 +02:00
# include <common/memleak.h>
2024-07-17 05:23:24 +02:00
# include <common/onion_message.h>
2024-05-09 06:42:38 +02:00
# include <errno.h>
2024-07-17 05:23:24 +02:00
# include <plugins/establish_onion_path.h>
2024-07-17 05:23:00 +02:00
# include <plugins/fetchinvoice.h>
2021-01-07 19:35:47 +01:00
# include <plugins/offers.h>
2022-11-09 03:32:01 +01:00
# include <plugins/offers_inv_hook.h>
2020-12-16 04:13:32 +01:00
# include <plugins/offers_invreq_hook.h>
2020-12-16 04:13:28 +01:00
# include <plugins/offers_offer.h>
2023-10-26 05:12:13 +02:00
# include <sodium.h>
# define HEADER_LEN crypto_secretstream_xchacha20poly1305_HEADERBYTES
# define ABYTES crypto_secretstream_xchacha20poly1305_ABYTES
2020-12-16 04:13:28 +01:00
2022-10-17 02:37:05 +02:00
struct pubkey id ;
2022-11-09 03:31:59 +01:00
u32 blockheight ;
2022-03-22 09:50:13 +01:00
u16 cltv_final ;
2021-08-02 01:56:12 +02:00
bool offers_enabled ;
2024-05-13 10:49:45 +02:00
bool disable_connect ;
2022-11-09 03:32:00 +01:00
struct secret invoicesecret_base ;
2024-07-17 05:23:25 +02:00
struct secret offerblinding_base ;
2024-05-09 06:42:38 +02:00
static struct gossmap * global_gossmap ;
static void init_gossmap ( struct plugin * plugin )
{
size_t num_cupdates_rejected ;
global_gossmap
= notleak_with_children ( gossmap_load ( plugin ,
GOSSIP_STORE_FILENAME ,
& num_cupdates_rejected ) ) ;
if ( ! global_gossmap )
plugin_err ( plugin , " Could not load gossmap %s: %s " ,
GOSSIP_STORE_FILENAME , strerror ( errno ) ) ;
if ( num_cupdates_rejected )
plugin_log ( plugin , LOG_DBG ,
" gossmap ignored %zu channel updates " ,
num_cupdates_rejected ) ;
}
2024-07-17 05:23:00 +02:00
struct gossmap * get_gossmap ( struct plugin * plugin )
2024-05-09 06:42:38 +02:00
{
if ( ! global_gossmap )
init_gossmap ( plugin ) ;
else
gossmap_refresh ( global_gossmap , NULL ) ;
return global_gossmap ;
}
2020-12-16 04:13:32 +01:00
2021-01-07 19:35:47 +01:00
static struct command_result * finished ( struct command * cmd ,
const char * buf ,
const jsmntok_t * result ,
void * unused )
{
return command_hook_success ( cmd ) ;
}
2024-07-17 05:23:24 +02:00
static struct command_result * injectonionmessage_error ( struct command * cmd ,
const char * buf ,
const jsmntok_t * err ,
void * unused )
2021-01-07 19:35:47 +01:00
{
2021-10-13 02:27:11 +02:00
/* This can happen if the peer goes offline or wasn't directly
* connected : " Unknown first peer " */
plugin_log ( cmd - > plugin , LOG_DBG ,
2024-07-17 05:23:24 +02:00
" injectonionmessage gave JSON error: %.*s " ,
2021-01-07 19:35:47 +01:00
json_tok_full_len ( err ) ,
json_tok_full ( buf , err ) ) ;
return command_hook_success ( cmd ) ;
}
2024-05-09 06:42:38 +02:00
/* So, you gave us a reply scid? Let's do the lookup then! And no,
* we won ' t accept private channels , just public ones .
*/
2024-05-13 10:49:45 +02:00
bool convert_to_scidd ( struct command * cmd ,
struct sciddir_or_pubkey * sciddpk )
2024-05-09 06:42:38 +02:00
{
struct gossmap * gossmap = get_gossmap ( cmd - > plugin ) ;
struct gossmap_chan * chan ;
struct gossmap_node * node ;
struct node_id id ;
chan = gossmap_find_chan ( gossmap , & sciddpk - > scidd . scid ) ;
if ( ! chan )
return false ;
node = gossmap_nth_node ( gossmap , chan , sciddpk - > scidd . dir ) ;
gossmap_node_get_id ( gossmap , node , & id ) ;
if ( ! sciddir_or_pubkey_from_node_id ( sciddpk , & id ) ) {
plugin_log ( cmd - > plugin , LOG_BROKEN ,
" Could not convert node %s to pubkey? " ,
fmt_node_id ( tmpctx , & id ) ) ;
return false ;
}
return true ;
}
2024-07-17 05:23:24 +02:00
struct command_result *
inject_onionmessage_ ( struct command * cmd ,
const struct onion_message * omsg ,
struct command_result * ( * cb ) ( struct command * command ,
const char * buf ,
const jsmntok_t * result ,
void * arg ) ,
struct command_result * ( * errcb ) ( struct command * command ,
const char * buf ,
const jsmntok_t * result ,
void * arg ) ,
void * arg )
{
struct out_req * req ;
req = jsonrpc_request_start ( cmd - > plugin , cmd , " injectonionmessage " ,
cb , errcb , arg ) ;
json_add_pubkey ( req - > js , " blinding " , & omsg - > first_blinding ) ;
json_array_start ( req - > js , " hops " ) ;
for ( size_t i = 0 ; i < tal_count ( omsg - > hops ) ; i + + ) {
json_object_start ( req - > js , NULL ) ;
json_add_pubkey ( req - > js , " id " , & omsg - > hops [ i ] - > pubkey ) ;
json_add_hex_talarr ( req - > js , " tlv " , omsg - > hops [ i ] - > raw_payload ) ;
json_object_end ( req - > js ) ;
}
json_array_end ( req - > js ) ;
return send_outreq ( cmd - > plugin , req ) ;
}
2024-07-17 05:23:24 +02:00
/* Holds the details while we wait for establish_onion_path to connect */
struct onion_reply {
struct blinded_path * reply_path ;
struct tlv_onionmsg_tlv * payload ;
} ;
static struct command_result * send_onion_reply_after_established ( struct command * cmd ,
const struct pubkey * path ,
struct onion_reply * onion_reply )
{
struct onion_message * omsg ;
omsg = outgoing_onion_message ( tmpctx , path , NULL , onion_reply - > reply_path , onion_reply - > payload ) ;
return inject_onionmessage ( cmd , omsg , finished , injectonionmessage_error , onion_reply ) ;
}
static struct command_result * send_onion_reply_not_established ( struct command * cmd ,
const char * why ,
struct onion_reply * onion_reply )
{
plugin_log ( cmd - > plugin , LOG_DBG ,
" Failed to connect for reply via %s: %s " ,
fmt_sciddir_or_pubkey ( tmpctx , & onion_reply - > reply_path - > first_node_id ) ,
why ) ;
return command_hook_success ( cmd ) ;
}
static struct blinded_path * blinded_path_dup ( const tal_t * ctx ,
const struct blinded_path * old )
{
struct blinded_path * bp = tal ( ctx , struct blinded_path ) ;
* bp = * old ;
bp - > path = tal_arr ( bp , struct onionmsg_hop * , tal_count ( old - > path ) ) ;
for ( size_t i = 0 ; i < tal_count ( bp - > path ) ; i + + ) {
bp - > path [ i ] = tal ( bp - > path , struct onionmsg_hop ) ;
bp - > path [ i ] - > blinded_node_id = old - > path [ i ] - > blinded_node_id ;
bp - > path [ i ] - > encrypted_recipient_data = tal_dup_talarr ( bp - > path [ i ] , u8 ,
old - > path [ i ] - > encrypted_recipient_data ) ;
}
return bp ;
}
2021-11-30 04:06:05 +01:00
struct command_result *
send_onion_reply ( struct command * cmd ,
2022-10-17 02:44:39 +02:00
struct blinded_path * reply_path ,
struct tlv_onionmsg_tlv * payload )
2021-11-30 04:06:05 +01:00
{
2024-07-17 05:23:24 +02:00
struct onion_reply * onion_reply ;
onion_reply = tal ( cmd , struct onion_reply ) ;
onion_reply - > reply_path = blinded_path_dup ( onion_reply , reply_path ) ;
onion_reply - > payload = tal_steal ( onion_reply , payload ) ;
if ( ! onion_reply - > reply_path - > first_node_id . is_pubkey ) {
if ( ! convert_to_scidd ( cmd , & onion_reply - > reply_path - > first_node_id ) ) {
plugin_log ( cmd - > plugin , LOG_DBG ,
" Cannot resolve initial reply scidd %s " ,
fmt_short_channel_id_dir ( tmpctx ,
& onion_reply - > reply_path - > first_node_id . scidd ) ) ;
return command_hook_success ( cmd ) ;
}
2024-05-09 06:42:38 +02:00
}
2024-07-17 05:23:24 +02:00
return establish_onion_path ( cmd , get_gossmap ( cmd - > plugin ) ,
& id , & onion_reply - > reply_path - > first_node_id . pubkey ,
disable_connect ,
send_onion_reply_after_established ,
send_onion_reply_not_established ,
onion_reply ) ;
2021-09-30 23:19:37 +02:00
}
2024-07-17 05:23:24 +02:00
static struct command_result * onion_message_recv ( struct command * cmd ,
const char * buf ,
const jsmntok_t * params )
2021-09-30 23:19:37 +02:00
{
2024-07-17 05:23:24 +02:00
const jsmntok_t * om , * secrettok , * replytok , * invreqtok , * invtok ;
2022-10-17 02:44:39 +02:00
struct blinded_path * reply_path = NULL ;
2024-07-17 05:23:24 +02:00
struct secret * secret ;
2021-09-30 23:19:37 +02:00
if ( ! offers_enabled )
return command_hook_success ( cmd ) ;
om = json_get_member ( buf , params , " onion_message " ) ;
2024-07-17 05:23:24 +02:00
secrettok = json_get_member ( buf , om , " pathsecret " ) ;
if ( secrettok ) {
secret = tal ( tmpctx , struct secret ) ;
json_to_secret ( buf , secrettok , secret ) ;
} else
secret = NULL ;
/* Might be reply for fetchinvoice (which always has a secret,
* so we can tell it ' s a response ) . */
if ( secret ) {
struct command_result * res ;
res = handle_invoice_onion_message ( cmd , buf , om , secret ) ;
if ( res )
return res ;
}
2021-09-30 23:19:37 +02:00
replytok = json_get_member ( buf , om , " reply_blindedpath " ) ;
if ( replytok ) {
2022-10-17 02:44:39 +02:00
reply_path = json_to_blinded_path ( cmd , buf , replytok ) ;
2022-09-29 05:49:03 +02:00
if ( ! reply_path )
plugin_err ( cmd - > plugin , " Invalid reply path %.*s? " ,
json_tok_full_len ( replytok ) ,
json_tok_full ( buf , replytok ) ) ;
2021-11-30 04:06:05 +01:00
}
2021-09-30 23:19:37 +02:00
invreqtok = json_get_member ( buf , om , " invoice_request " ) ;
if ( invreqtok ) {
2021-11-30 04:06:04 +01:00
const u8 * invreqbin = json_tok_bin_from_hex ( tmpctx , buf , invreqtok ) ;
2022-09-29 05:49:03 +02:00
if ( reply_path )
2021-11-30 04:06:04 +01:00
return handle_invoice_request ( cmd ,
invreqbin ,
2024-07-17 05:23:25 +02:00
reply_path , secret ) ;
2021-09-30 23:19:37 +02:00
else
plugin_log ( cmd - > plugin , LOG_DBG ,
" invoice_request without reply_path " ) ;
}
2022-11-09 03:32:01 +01:00
invtok = json_get_member ( buf , om , " invoice " ) ;
if ( invtok ) {
const u8 * invbin = json_tok_bin_from_hex ( tmpctx , buf , invtok ) ;
if ( invbin )
2024-07-17 05:23:25 +02:00
return handle_invoice ( cmd , invbin , reply_path , secret ) ;
2022-11-09 03:32:01 +01:00
}
2020-12-16 04:13:32 +01:00
return command_hook_success ( cmd ) ;
}
2020-12-16 04:13:23 +01:00
2024-07-17 11:02:11 +02:00
struct find_best_peer_data {
struct command_result * ( * cb ) ( struct command * ,
const struct chaninfo * ,
void * ) ;
int needed_feature ;
void * arg ;
} ;
static struct command_result * listincoming_done ( struct command * cmd ,
const char * buf ,
const jsmntok_t * result ,
struct find_best_peer_data * data )
{
const jsmntok_t * arr , * t ;
size_t i ;
struct chaninfo * best = NULL ;
arr = json_get_member ( buf , result , " incoming " ) ;
json_for_each_arr ( i , t , arr ) {
struct chaninfo ci ;
const jsmntok_t * pftok ;
u8 * features ;
const char * err ;
struct amount_msat feebase ;
err = json_scan ( tmpctx , buf , t ,
" {id:%, "
" incoming_capacity_msat:%, "
" htlc_min_msat:%, "
" htlc_max_msat:%, "
" fee_base_msat:%, "
" fee_proportional_millionths:%, "
" cltv_expiry_delta:%} " ,
JSON_SCAN ( json_to_pubkey , & ci . id ) ,
JSON_SCAN ( json_to_msat , & ci . capacity ) ,
JSON_SCAN ( json_to_msat , & ci . htlc_min ) ,
JSON_SCAN ( json_to_msat , & ci . htlc_max ) ,
JSON_SCAN ( json_to_msat , & feebase ) ,
JSON_SCAN ( json_to_u32 , & ci . feeppm ) ,
JSON_SCAN ( json_to_u32 , & ci . cltv ) ) ;
if ( err ) {
plugin_log ( cmd - > plugin , LOG_BROKEN ,
" Could not parse listincoming: %s " ,
err ) ;
continue ;
}
ci . feebase = feebase . millisatoshis ; /* Raw: feebase */
/* Not presented if there's no channel_announcement for peer:
* we could use listpeers , but if it ' s private we probably
* don ' t want to blinded route through it ! */
pftok = json_get_member ( buf , t , " peer_features " ) ;
if ( ! pftok )
continue ;
features = json_tok_bin_from_hex ( tmpctx , buf , pftok ) ;
if ( ! feature_offered ( features , data - > needed_feature ) )
continue ;
if ( ! best | | amount_msat_greater ( ci . capacity , best - > capacity ) )
best = tal_dup ( tmpctx , struct chaninfo , & ci ) ;
}
/* Free data if they don't */
tal_steal ( tmpctx , data ) ;
return data - > cb ( cmd , best , data - > arg ) ;
}
struct command_result * find_best_peer_ ( struct command * cmd ,
int needed_feature ,
struct command_result * ( * cb ) ( struct command * ,
const struct chaninfo * ,
void * ) ,
void * arg )
{
struct out_req * req ;
struct find_best_peer_data * data = tal ( cmd , struct find_best_peer_data ) ;
data - > cb = cb ;
data - > arg = arg ;
data - > needed_feature = needed_feature ;
req = jsonrpc_request_start ( cmd - > plugin , cmd , " listincoming " ,
listincoming_done , forward_error , data ) ;
return send_outreq ( cmd - > plugin , req ) ;
}
2020-12-16 04:13:23 +01:00
static const struct plugin_hook hooks [ ] = {
2021-09-30 23:19:37 +02:00
{
2022-11-09 02:30:10 +01:00
" onion_message_recv " ,
2024-07-17 05:23:24 +02:00
onion_message_recv
2021-09-30 23:19:37 +02:00
} ,
2024-07-17 05:23:00 +02:00
{
" onion_message_recv_secret " ,
2024-07-17 05:23:24 +02:00
onion_message_recv
2024-07-17 05:23:00 +02:00
} ,
{
" invoice_payment " ,
invoice_payment ,
} ,
2020-12-16 04:13:23 +01:00
} ;
2022-11-09 03:31:59 +01:00
static struct command_result * block_added_notify ( struct command * cmd ,
const char * buf ,
const jsmntok_t * params )
{
json_scan ( cmd , buf , params , " {block:{height:%}} " ,
JSON_SCAN ( json_to_u32 , & blockheight ) ) ;
return notification_handled ( cmd ) ;
}
static const struct plugin_notification notifications [ ] = {
{
" block_added " ,
block_added_notify ,
} ,
} ;
2021-01-07 19:48:47 +01:00
struct decodable {
const char * type ;
struct bolt11 * b11 ;
struct tlv_offer * offer ;
struct tlv_invoice * invoice ;
struct tlv_invoice_request * invreq ;
2022-07-16 15:18:27 +02:00
struct rune * rune ;
2023-10-26 05:12:13 +02:00
u8 * emergency_recover ;
2021-01-07 19:48:47 +01:00
} ;
2023-10-26 05:12:13 +02:00
static u8 * encrypted_decode ( const tal_t * ctx , const char * str , char * * fail ) {
if ( strlen ( str ) < 8 ) {
* fail = tal_fmt ( ctx , " invalid payload " ) ;
return NULL ;
}
size_t hrp_maxlen = strlen ( str ) - 6 ;
char * hrp = tal_arr ( ctx , char , hrp_maxlen ) ;
size_t data_maxlen = strlen ( str ) - 8 ;
u5 * data = tal_arr ( ctx , u5 , data_maxlen ) ;
size_t datalen = 0 ;
if ( bech32_decode ( hrp , data , & datalen , str , ( size_t ) - 1 )
= = BECH32_ENCODING_NONE ) {
* fail = tal_fmt ( ctx , " invalid bech32 encoding " ) ;
goto fail ;
}
if ( ! streq ( hrp , " clnemerg " ) ) {
* fail = tal_fmt ( ctx , " hrp should be `clnemerg` " ) ;
goto fail ;
}
u8 * data8bit = tal_arr ( data , u8 , 0 ) ;
bech32_pull_bits ( & data8bit , data , datalen * 5 ) ;
return data8bit ;
fail :
tal_free ( data ) ;
return NULL ;
}
2021-01-07 19:48:47 +01:00
static struct command_result * param_decodable ( struct command * cmd ,
const char * name ,
const char * buffer ,
const jsmntok_t * token ,
struct decodable * decodable )
{
char * likely_fail = NULL , * fail ;
jsmntok_t tok ;
/* BOLT #11:
*
* If a URI scheme is desired , the current recommendation is to either
* use ' lightning : ' as a prefix before the BOLT - 11 encoding
*/
tok = * token ;
if ( json_tok_startswith ( buffer , & tok , " lightning: " )
| | json_tok_startswith ( buffer , & tok , " LIGHTNING: " ) )
tok . start + = strlen ( " lightning: " ) ;
decodable - > offer = offer_decode ( cmd , buffer + tok . start ,
tok . end - tok . start ,
plugin_feature_set ( cmd - > plugin ) , NULL ,
json_tok_startswith ( buffer , & tok , " lno1 " )
? & likely_fail : & fail ) ;
if ( decodable - > offer ) {
decodable - > type = " bolt12 offer " ;
return NULL ;
}
decodable - > invoice = invoice_decode ( cmd , buffer + tok . start ,
tok . end - tok . start ,
plugin_feature_set ( cmd - > plugin ) ,
NULL ,
json_tok_startswith ( buffer , & tok ,
" lni1 " )
? & likely_fail : & fail ) ;
if ( decodable - > invoice ) {
decodable - > type = " bolt12 invoice " ;
return NULL ;
}
decodable - > invreq = invrequest_decode ( cmd , buffer + tok . start ,
tok . end - tok . start ,
plugin_feature_set ( cmd - > plugin ) ,
NULL ,
json_tok_startswith ( buffer , & tok ,
" lnr1 " )
? & likely_fail : & fail ) ;
if ( decodable - > invreq ) {
decodable - > type = " bolt12 invoice_request " ;
return NULL ;
}
2023-10-26 05:12:13 +02:00
decodable - > emergency_recover = encrypted_decode ( cmd , tal_strndup ( tmpctx , buffer + tok . start ,
tok . end - tok . start ) ,
json_tok_startswith ( buffer , & tok ,
" clnemerg1 " )
? & likely_fail : & fail ) ;
if ( decodable - > emergency_recover ) {
decodable - > type = " emergency recover " ;
return NULL ;
}
2021-01-07 19:48:47 +01:00
/* If no other was likely, bolt11 decoder gives us failure string. */
decodable - > b11 = bolt11_decode ( cmd ,
tal_strndup ( tmpctx , buffer + tok . start ,
tok . end - tok . start ) ,
plugin_feature_set ( cmd - > plugin ) ,
NULL , NULL ,
likely_fail ? & fail : & likely_fail ) ;
if ( decodable - > b11 ) {
decodable - > type = " bolt11 invoice " ;
return NULL ;
}
2022-07-16 15:18:27 +02:00
decodable - > rune = rune_from_base64n ( decodable , buffer + tok . start ,
tok . end - tok . start ) ;
if ( decodable - > rune ) {
decodable - > type = " rune " ;
return NULL ;
}
2021-01-07 19:48:47 +01:00
/* Return failure message from most likely parsing candidate */
return command_fail_badparam ( cmd , name , buffer , & tok , likely_fail ) ;
}
static void json_add_chains ( struct json_stream * js ,
2022-11-09 03:32:00 +01:00
const char * fieldname ,
2021-01-07 19:48:47 +01:00
const struct bitcoin_blkid * chains )
{
2022-11-09 03:32:00 +01:00
json_array_start ( js , fieldname ) ;
2021-01-07 19:48:47 +01:00
for ( size_t i = 0 ; i < tal_count ( chains ) ; i + + )
json_add_sha256 ( js , NULL , & chains [ i ] . shad . sha ) ;
json_array_end ( js ) ;
}
static void json_add_onionmsg_path ( struct json_stream * js ,
const char * fieldname ,
2023-01-12 04:58:03 +01:00
const struct onionmsg_hop * hop )
2021-01-07 19:48:47 +01:00
{
json_object_start ( js , fieldname ) ;
2022-10-17 02:44:39 +02:00
json_add_pubkey ( js , " blinded_node_id " , & hop - > blinded_node_id ) ;
json_add_hex_talarr ( js , " encrypted_recipient_data " , hop - > encrypted_recipient_data ) ;
2021-01-07 19:48:47 +01:00
json_object_end ( js ) ;
}
2021-05-26 07:46:01 +02:00
/* Returns true if valid */
static bool json_add_blinded_paths ( struct json_stream * js ,
2022-11-09 03:32:00 +01:00
const char * fieldname ,
2021-01-07 19:48:47 +01:00
struct blinded_path * * paths ,
struct blinded_payinfo * * blindedpay )
{
2022-11-09 03:32:00 +01:00
json_array_start ( js , fieldname ) ;
2021-01-07 19:48:47 +01:00
for ( size_t i = 0 ; i < tal_count ( paths ) ; i + + ) {
json_object_start ( js , NULL ) ;
2024-05-09 05:36:20 +02:00
if ( paths [ i ] - > first_node_id . is_pubkey ) {
json_add_pubkey ( js , " first_node_id " ,
& paths [ i ] - > first_node_id . pubkey ) ;
} else {
json_add_short_channel_id ( js , " first_scid " ,
paths [ i ] - > first_node_id . scidd . scid ) ;
json_add_u32 ( js , " first_scid_dir " ,
paths [ i ] - > first_node_id . scidd . dir ) ;
}
2021-01-07 19:48:47 +01:00
json_add_pubkey ( js , " blinding " , & paths [ i ] - > blinding ) ;
2023-01-12 04:58:03 +01:00
/* Don't crash if we're short a payinfo! */
if ( i < tal_count ( blindedpay ) ) {
json_object_start ( js , " payinfo " ) ;
2023-03-14 06:21:50 +01:00
json_add_amount_msat ( js , " fee_base_msat " ,
2023-01-12 04:58:03 +01:00
amount_msat ( blindedpay [ i ] - > fee_base_msat ) ) ;
json_add_u32 ( js , " fee_proportional_millionths " ,
blindedpay [ i ] - > fee_proportional_millionths ) ;
json_add_u32 ( js , " cltv_expiry_delta " ,
blindedpay [ i ] - > cltv_expiry_delta ) ;
json_add_hex_talarr ( js , " features " , blindedpay [ i ] - > features ) ;
json_object_end ( js ) ;
}
2021-01-07 19:48:47 +01:00
json_array_start ( js , " path " ) ;
for ( size_t j = 0 ; j < tal_count ( paths [ i ] - > path ) ; j + + ) {
2023-01-12 04:58:03 +01:00
json_add_onionmsg_path ( js , NULL , paths [ i ] - > path [ j ] ) ;
2021-01-07 19:48:47 +01:00
}
json_array_end ( js ) ;
json_object_end ( js ) ;
}
json_array_end ( js ) ;
/* BOLT-offers #12:
2022-11-09 03:32:01 +01:00
* - MUST reject the invoice if ` invoice_blindedpay ` does not contain
* exactly one ` blinded_payinfo ` per ` invoice_paths ` . ` blinded_path ` .
2021-01-07 19:48:47 +01:00
*/
2023-01-12 04:58:03 +01:00
if ( blindedpay & & tal_count ( blindedpay ) ! = tal_count ( paths ) ) {
json_add_str_fmt ( js , " warning_invalid_invoice_blindedpay " ,
" invoice has %zu blinded_payinfo but %zu paths " ,
tal_count ( blindedpay ) , tal_count ( paths ) ) ;
2021-05-26 07:46:01 +02:00
return false ;
}
return true ;
2021-01-07 19:48:47 +01:00
}
static const char * recurrence_time_unit_name ( u8 time_unit )
{
2021-11-30 04:06:05 +01:00
/* BOLT-offers-recurrence #12:
2021-01-07 19:48:47 +01:00
* ` time_unit ` defining 0 ( seconds ) , 1 ( days ) , 2 ( months ) , 3 ( years ) .
*/
switch ( time_unit ) {
case 0 :
return " seconds " ;
case 1 :
return " days " ;
case 2 :
return " months " ;
case 3 :
return " years " ;
}
return NULL ;
}
2022-11-09 03:32:00 +01:00
static bool json_add_utf8 ( struct json_stream * js ,
const char * fieldname ,
const char * utf8str )
{
if ( utf8_check ( utf8str , tal_bytelen ( utf8str ) ) ) {
json_add_stringn ( js , fieldname , utf8str , tal_bytelen ( utf8str ) ) ;
return true ;
}
json_add_string ( js , tal_fmt ( tmpctx , " warning_invalid_%s " , fieldname ) ,
" invalid UTF8 " ) ;
return false ;
}
static bool json_add_offer_fields ( struct json_stream * js ,
const struct bitcoin_blkid * offer_chains ,
const u8 * offer_metadata ,
const char * offer_currency ,
const u64 * offer_amount ,
const char * offer_description ,
const u8 * offer_features ,
const u64 * offer_absolute_expiry ,
struct blinded_path * * offer_paths ,
const char * offer_issuer ,
const u64 * offer_quantity_max ,
const struct pubkey * offer_node_id ,
const struct recurrence * offer_recurrence ,
const struct recurrence_paywindow * offer_recurrence_paywindow ,
const u32 * offer_recurrence_limit ,
const struct recurrence_base * offer_recurrence_base )
2021-01-07 19:48:47 +01:00
{
2021-05-26 07:46:01 +02:00
bool valid = true ;
2021-01-07 19:48:47 +01:00
2022-11-09 03:32:00 +01:00
if ( offer_chains )
json_add_chains ( js , " offer_chains " , offer_chains ) ;
if ( offer_metadata )
json_add_hex_talarr ( js , " offer_metadata " , offer_metadata ) ;
if ( offer_currency ) {
2021-01-07 19:48:47 +01:00
const struct iso4217_name_and_divisor * iso4217 ;
2022-11-09 03:32:00 +01:00
valid & = json_add_utf8 ( js , " offer_currency " , offer_currency ) ;
if ( offer_amount )
json_add_u64 ( js , " offer_amount " , * offer_amount ) ;
iso4217 = find_iso4217 ( offer_currency ,
tal_bytelen ( offer_currency ) ) ;
2021-01-07 19:48:47 +01:00
if ( iso4217 )
2022-11-09 03:32:00 +01:00
json_add_num ( js , " currency_minor_unit " , iso4217 - > minor_unit ) ;
2021-01-07 19:48:47 +01:00
else
2022-11-09 03:32:00 +01:00
json_add_string ( js , " warning_unknown_offer_currency " ,
2021-01-07 19:48:47 +01:00
" unknown currency code " ) ;
2022-11-09 03:32:00 +01:00
} else if ( offer_amount )
2023-03-14 06:21:50 +01:00
json_add_amount_msat ( js , " offer_amount_msat " ,
amount_msat ( * offer_amount ) ) ;
2021-01-07 19:48:47 +01:00
/* BOLT-offers #12:
2024-06-29 13:38:36 +02:00
*
* - if offer_amount is set and offer_description is not set :
* - MUST NOT respond to the offer .
2021-01-07 19:48:47 +01:00
*/
2022-11-09 03:32:00 +01:00
if ( offer_description )
valid & = json_add_utf8 ( js , " offer_description " ,
offer_description ) ;
2024-06-29 13:38:36 +02:00
else if ( ! offer_description & & offer_amount ) {
2022-11-09 03:32:00 +01:00
json_add_string ( js , " warning_missing_offer_description " ,
2024-06-29 13:38:36 +02:00
" description is required for the user to know what it was they paid for " ) ;
2021-05-26 07:46:01 +02:00
valid = false ;
}
2021-01-07 19:48:47 +01:00
2022-11-09 03:32:00 +01:00
if ( offer_issuer )
valid & = json_add_utf8 ( js , " offer_issuer " , offer_issuer ) ;
if ( offer_features )
json_add_hex_talarr ( js , " offer_features " , offer_features ) ;
if ( offer_absolute_expiry )
json_add_u64 ( js , " offer_absolute_expiry " ,
* offer_absolute_expiry ) ;
if ( offer_paths )
valid & = json_add_blinded_paths ( js , " offer_paths " ,
offer_paths , NULL ) ;
if ( offer_quantity_max )
json_add_u64 ( js , " offer_quantity_max " , * offer_quantity_max ) ;
if ( offer_recurrence ) {
2021-01-07 19:48:47 +01:00
const char * name ;
2022-11-09 03:32:00 +01:00
json_object_start ( js , " offer_recurrence " ) ;
json_add_num ( js , " time_unit " , offer_recurrence - > time_unit ) ;
name = recurrence_time_unit_name ( offer_recurrence - > time_unit ) ;
2021-01-07 19:48:47 +01:00
if ( name )
json_add_string ( js , " time_unit_name " , name ) ;
2022-11-09 03:32:00 +01:00
json_add_num ( js , " period " , offer_recurrence - > period ) ;
if ( offer_recurrence_base ) {
2021-01-07 19:48:47 +01:00
json_add_u64 ( js , " basetime " ,
2022-11-09 03:32:00 +01:00
offer_recurrence_base - > basetime ) ;
if ( offer_recurrence_base - > start_any_period )
2021-01-07 19:48:47 +01:00
json_add_bool ( js , " start_any_period " , true ) ;
}
2022-11-09 03:32:00 +01:00
if ( offer_recurrence_limit )
json_add_u32 ( js , " limit " , * offer_recurrence_limit ) ;
if ( offer_recurrence_paywindow ) {
2021-01-07 19:48:47 +01:00
json_object_start ( js , " paywindow " ) ;
json_add_u32 ( js , " seconds_before " ,
2022-11-09 03:32:00 +01:00
offer_recurrence_paywindow - > seconds_before ) ;
2021-01-07 19:48:47 +01:00
json_add_u32 ( js , " seconds_after " ,
2022-11-09 03:32:00 +01:00
offer_recurrence_paywindow - > seconds_after ) ;
if ( offer_recurrence_paywindow - > proportional_amount )
2021-01-07 19:48:47 +01:00
json_add_bool ( js , " proportional_amount " , true ) ;
json_object_end ( js ) ;
}
json_object_end ( js ) ;
}
2022-11-09 03:32:00 +01:00
/* Required for offers, *not* for others! */
if ( offer_node_id )
json_add_pubkey ( js , " offer_node_id " , offer_node_id ) ;
return valid ;
}
2022-11-09 03:32:01 +01:00
static void json_add_extra_fields ( struct json_stream * js ,
const char * fieldname ,
const struct tlv_field * fields )
{
bool have_extra = false ;
for ( size_t i = 0 ; i < tal_count ( fields ) ; i + + ) {
if ( fields [ i ] . meta )
continue ;
if ( ! have_extra ) {
json_array_start ( js , fieldname ) ;
have_extra = true ;
}
json_object_start ( js , NULL ) ;
json_add_u64 ( js , " type " , fields [ i ] . numtype ) ;
json_add_u64 ( js , " length " , fields [ i ] . length ) ;
json_add_hex ( js , " value " ,
fields [ i ] . value , fields [ i ] . length ) ;
}
if ( have_extra )
json_array_end ( js ) ;
}
2022-11-09 03:32:00 +01:00
static void json_add_offer ( struct json_stream * js , const struct tlv_offer * offer )
{
struct sha256 offer_id ;
bool valid = true ;
offer_offer_id ( offer , & offer_id ) ;
json_add_sha256 ( js , " offer_id " , & offer_id ) ;
valid & = json_add_offer_fields ( js ,
offer - > offer_chains ,
offer - > offer_metadata ,
offer - > offer_currency ,
offer - > offer_amount ,
offer - > offer_description ,
offer - > offer_features ,
offer - > offer_absolute_expiry ,
offer - > offer_paths ,
offer - > offer_issuer ,
offer - > offer_quantity_max ,
offer - > offer_node_id ,
offer - > offer_recurrence ,
offer - > offer_recurrence_paywindow ,
offer - > offer_recurrence_limit ,
offer - > offer_recurrence_base ) ;
2022-11-09 03:32:00 +01:00
/* BOLT-offers #12:
* - if ` offer_node_id ` is not set :
* - MUST NOT respond to the offer .
*/
2022-11-09 03:32:00 +01:00
if ( ! offer - > offer_node_id ) {
json_add_string ( js , " warning_missing_offer_node_id " ,
" offers without a node_id are invalid " ) ;
2021-07-05 08:23:24 +02:00
valid = false ;
2022-11-09 03:32:00 +01:00
}
2022-11-09 03:32:01 +01:00
json_add_extra_fields ( js , " unknown_offer_tlvs " , offer - > fields ) ;
2021-05-26 07:46:01 +02:00
json_add_bool ( js , " valid " , valid ) ;
2021-01-07 19:48:47 +01:00
}
2022-11-09 03:32:00 +01:00
static bool json_add_invreq_fields ( struct json_stream * js ,
const u8 * invreq_metadata ,
const struct bitcoin_blkid * invreq_chain ,
const u64 * invreq_amount ,
const u8 * invreq_features ,
const u64 * invreq_quantity ,
const struct pubkey * invreq_payer_id ,
const utf8 * invreq_payer_note ,
const u32 * invreq_recurrence_counter ,
const u32 * invreq_recurrence_start )
{
bool valid = true ;
/* BOLT-offers #12:
* - MUST fail the request if ` invreq_payer_id ` or ` invreq_metadata ` are not present .
*/
if ( invreq_metadata )
json_add_hex_talarr ( js , " invreq_metadata " ,
invreq_metadata ) ;
else {
json_add_string ( js , " warning_missing_invreq_metadata " ,
" invreq_metadata required " ) ;
valid = false ;
}
/* This can be missing for an invoice though! */
if ( invreq_payer_id )
json_add_pubkey ( js , " invreq_payer_id " , invreq_payer_id ) ;
if ( invreq_chain )
json_add_sha256 ( js , " invreq_chain " , & invreq_chain - > shad . sha ) ;
if ( invreq_amount )
2023-03-14 06:21:50 +01:00
json_add_amount_msat ( js , " invreq_amount_msat " ,
amount_msat ( * invreq_amount ) ) ;
2022-11-09 03:32:00 +01:00
if ( invreq_features )
json_add_hex_talarr ( js , " invreq_features " , invreq_features ) ;
if ( invreq_quantity )
json_add_u64 ( js , " invreq_quantity " , * invreq_quantity ) ;
if ( invreq_payer_note )
valid & = json_add_utf8 ( js , " invreq_payer_note " , invreq_payer_note ) ;
if ( invreq_recurrence_counter ) {
json_add_u32 ( js , " invreq_recurrence_counter " ,
* invreq_recurrence_counter ) ;
if ( invreq_recurrence_start )
json_add_u32 ( js , " invreq_recurrence_start " ,
* invreq_recurrence_start ) ;
}
return valid ;
}
2021-05-26 07:46:01 +02:00
/* Returns true if valid */
static bool json_add_fallback_address ( struct json_stream * js ,
2021-01-07 19:48:47 +01:00
const struct chainparams * chain ,
u8 version , const u8 * address )
{
2021-11-29 08:10:06 +01:00
char out [ 73 + strlen ( chain - > onchain_hrp ) ] ;
2021-01-07 19:48:47 +01:00
/* Does extra checks, in particular checks v0 sizes */
2021-11-29 08:10:06 +01:00
if ( segwit_addr_encode ( out , chain - > onchain_hrp , version ,
2021-05-26 07:46:01 +02:00
address , tal_bytelen ( address ) ) ) {
2021-01-07 19:48:47 +01:00
json_add_string ( js , " address " , out ) ;
2021-05-26 07:46:01 +02:00
return true ;
}
json_add_string ( js ,
2022-11-09 03:32:00 +01:00
" warning_invalid_invoice_fallbacks_address " ,
2021-05-26 07:46:01 +02:00
" invalid fallback address for this version " ) ;
return false ;
2021-01-07 19:48:47 +01:00
}
2021-05-26 07:46:01 +02:00
/* Returns true if valid */
static bool json_add_fallbacks ( struct json_stream * js ,
2021-01-07 19:48:47 +01:00
const struct bitcoin_blkid * chains ,
struct fallback_address * * fallbacks )
{
const struct chainparams * chain ;
2021-05-26 07:46:01 +02:00
bool valid = true ;
2021-01-07 19:48:47 +01:00
/* Present address as first chain mentioned. */
if ( tal_count ( chains ) ! = 0 )
chain = chainparams_by_chainhash ( & chains [ 0 ] ) ;
else
chain = chainparams_for_network ( " bitcoin " ) ;
2022-11-09 03:32:00 +01:00
json_array_start ( js , " invoice_fallbacks " ) ;
2021-01-07 19:48:47 +01:00
for ( size_t i = 0 ; i < tal_count ( fallbacks ) ; i + + ) {
size_t addrlen = tal_bytelen ( fallbacks [ i ] - > address ) ;
json_object_start ( js , NULL ) ;
json_add_u32 ( js , " version " , fallbacks [ i ] - > version ) ;
json_add_hex_talarr ( js , " hex " , fallbacks [ i ] - > address ) ;
/* BOLT-offers #12:
2022-11-09 03:32:01 +01:00
* - for the bitcoin chain , if the invoice specifies ` invoice_fallbacks ` :
2021-01-07 19:48:47 +01:00
* - MUST ignore any ` fallback_address ` for which ` version ` is
* greater than 16.
* - MUST ignore any ` fallback_address ` for which ` address ` is
* less than 2 or greater than 40 bytes .
* - MUST ignore any ` fallback_address ` for which ` address ` does
* not meet known requirements for the given ` version `
*/
if ( fallbacks [ i ] - > version > 16 ) {
json_add_string ( js ,
2022-11-09 03:32:00 +01:00
" warning_invalid_invoice_fallbacks_version " ,
2021-01-07 19:48:47 +01:00
" invoice fallback version > 16 " ) ;
2021-05-26 07:46:01 +02:00
valid = false ;
2021-01-07 19:48:47 +01:00
} else if ( addrlen < 2 | | addrlen > 40 ) {
json_add_string ( js ,
2022-11-09 03:32:00 +01:00
" warning_invalid_invoice_fallbacks_address " ,
2021-01-07 19:48:47 +01:00
" invoice fallback address bad length " ) ;
2021-05-26 07:46:01 +02:00
valid = false ;
2021-01-07 19:48:47 +01:00
} else if ( chain ) {
2021-05-26 07:46:01 +02:00
valid & = json_add_fallback_address ( js , chain ,
fallbacks [ i ] - > version ,
fallbacks [ i ] - > address ) ;
2021-01-07 19:48:47 +01:00
}
json_object_end ( js ) ;
}
json_array_end ( js ) ;
2021-05-26 07:46:01 +02:00
return valid ;
2021-01-07 19:48:47 +01:00
}
2022-11-09 03:32:00 +01:00
static void json_add_invoice_request ( struct json_stream * js ,
const struct tlv_invoice_request * invreq )
2021-01-07 19:48:47 +01:00
{
2021-05-26 07:46:01 +02:00
bool valid = true ;
2022-11-09 03:32:00 +01:00
/* If there's an offer_node_id, then there's an offer. */
2022-11-09 03:32:00 +01:00
if ( invreq - > offer_node_id ) {
2022-11-09 03:32:00 +01:00
struct sha256 offer_id ;
2022-11-09 03:32:00 +01:00
invreq_offer_id ( invreq , & offer_id ) ;
2022-11-09 03:32:00 +01:00
json_add_sha256 ( js , " offer_id " , & offer_id ) ;
}
2022-11-09 03:32:00 +01:00
valid & = json_add_offer_fields ( js ,
invreq - > offer_chains ,
invreq - > offer_metadata ,
invreq - > offer_currency ,
invreq - > offer_amount ,
invreq - > offer_description ,
invreq - > offer_features ,
invreq - > offer_absolute_expiry ,
invreq - > offer_paths ,
invreq - > offer_issuer ,
invreq - > offer_quantity_max ,
invreq - > offer_node_id ,
invreq - > offer_recurrence ,
invreq - > offer_recurrence_paywindow ,
invreq - > offer_recurrence_limit ,
invreq - > offer_recurrence_base ) ;
valid & = json_add_invreq_fields ( js ,
invreq - > invreq_metadata ,
invreq - > invreq_chain ,
invreq - > invreq_amount ,
invreq - > invreq_features ,
invreq - > invreq_quantity ,
invreq - > invreq_payer_id ,
invreq - > invreq_payer_note ,
invreq - > invreq_recurrence_counter ,
invreq - > invreq_recurrence_start ) ;
2021-01-07 19:48:47 +01:00
/* BOLT-offers #12:
2022-11-09 03:32:00 +01:00
* - MUST fail the request if ` invreq_payer_id ` or ` invreq_metadata ` are not present .
2021-01-07 19:48:47 +01:00
*/
2022-11-09 03:32:00 +01:00
if ( ! invreq - > invreq_payer_id ) {
json_add_string ( js , " warning_missing_invreq_payer_id " ,
" invreq_payer_id required " ) ;
2022-11-09 03:32:00 +01:00
valid = false ;
}
2021-01-07 19:48:47 +01:00
/* BOLT-offers #12:
2022-11-09 03:32:00 +01:00
* - MUST fail the request if ` signature ` is not correct as detailed
* in [ Signature Calculation ] ( # signature - calculation ) using the
* ` invreq_payer_id ` .
2021-01-07 19:48:47 +01:00
*/
2022-11-09 03:32:00 +01:00
if ( invreq - > signature ) {
if ( invreq - > invreq_payer_id
& & ! bolt12_check_signature ( invreq - > fields ,
" invoice_request " ,
" signature " ,
invreq - > invreq_payer_id ,
invreq - > signature ) ) {
json_add_string ( js , " warning_invalid_invoice_request_signature " ,
" Bad signature " ) ;
valid = false ;
} else {
json_add_bip340sig ( js , " signature " , invreq - > signature ) ;
}
2022-11-09 03:32:00 +01:00
} else {
2022-11-09 03:32:00 +01:00
json_add_string ( js , " warning_missing_invoice_request_signature " ,
" Missing signature " ) ;
2022-11-09 03:32:00 +01:00
valid = false ;
}
2022-11-09 03:32:01 +01:00
json_add_extra_fields ( js , " unknown_invoice_request_tlvs " , invreq - > fields ) ;
2022-11-09 03:32:00 +01:00
json_add_bool ( js , " valid " , valid ) ;
}
static void json_add_b12_invoice ( struct json_stream * js ,
const struct tlv_invoice * invoice )
{
bool valid = true ;
/* If there's an offer_node_id, then there's an offer. */
if ( invoice - > offer_node_id ) {
struct sha256 offer_id ;
invoice_offer_id ( invoice , & offer_id ) ;
json_add_sha256 ( js , " offer_id " , & offer_id ) ;
2022-11-09 03:32:00 +01:00
}
2022-11-09 03:32:00 +01:00
valid & = json_add_offer_fields ( js ,
invoice - > offer_chains ,
invoice - > offer_metadata ,
invoice - > offer_currency ,
invoice - > offer_amount ,
invoice - > offer_description ,
invoice - > offer_features ,
invoice - > offer_absolute_expiry ,
invoice - > offer_paths ,
invoice - > offer_issuer ,
invoice - > offer_quantity_max ,
invoice - > offer_node_id ,
invoice - > offer_recurrence ,
invoice - > offer_recurrence_paywindow ,
invoice - > offer_recurrence_limit ,
invoice - > offer_recurrence_base ) ;
valid & = json_add_invreq_fields ( js ,
invoice - > invreq_metadata ,
invoice - > invreq_chain ,
invoice - > invreq_amount ,
invoice - > invreq_features ,
invoice - > invreq_quantity ,
invoice - > invreq_payer_id ,
invoice - > invreq_payer_note ,
invoice - > invreq_recurrence_counter ,
invoice - > invreq_recurrence_start ) ;
2022-11-09 03:32:00 +01:00
/* BOLT-offers #12:
* - MUST reject the invoice if ` invoice_paths ` is not present
* or is empty .
* - MUST reject the invoice if ` invoice_blindedpay ` is not present .
* - MUST reject the invoice if ` invoice_blindedpay ` does not contain
* exactly one ` blinded_payinfo ` per ` invoice_paths ` . ` blinded_path ` .
*/
if ( invoice - > invoice_paths ) {
if ( ! invoice - > invoice_blindedpay ) {
2022-11-09 03:32:00 +01:00
json_add_string ( js , " warning_missing_invoice_blindedpay " ,
" invoices with paths without blindedpay are invalid " ) ;
2021-05-26 07:46:01 +02:00
valid = false ;
}
2022-11-09 03:32:00 +01:00
valid & = json_add_blinded_paths ( js , " invoice_paths " ,
invoice - > invoice_paths ,
2022-11-09 03:32:00 +01:00
invoice - > invoice_blindedpay ) ;
} else {
2022-11-09 03:32:00 +01:00
json_add_string ( js , " warning_missing_invoice_paths " ,
" invoices without a invoice_paths are invalid " ) ;
2022-11-09 03:32:00 +01:00
valid = false ;
2021-01-07 19:48:47 +01:00
}
2022-11-09 03:32:00 +01:00
2022-11-09 03:32:00 +01:00
if ( invoice - > invoice_created_at ) {
json_add_u64 ( js , " invoice_created_at " , * invoice - > invoice_created_at ) ;
} else {
json_add_string ( js , " warning_missing_invoice_created_at " ,
" invoices without created_at are invalid " ) ;
valid = false ;
2021-01-07 19:48:47 +01:00
}
/* BOLT-offers #12:
*
2022-11-09 03:32:00 +01:00
* - if ` invoice_relative_expiry ` is present :
* - MUST reject the invoice if the current time since 1970 - 01 - 01 UTC
* is greater than ` invoice_created_at ` plus ` seconds_from_creation ` .
* - otherwise :
* - MUST reject the invoice if the current time since 1970 - 01 - 01 UTC
* is greater than ` invoice_created_at ` plus 7200.
2021-01-07 19:48:47 +01:00
*/
2022-11-09 03:32:00 +01:00
if ( invoice - > invoice_relative_expiry )
2022-11-09 03:32:00 +01:00
json_add_u32 ( js , " invoice_relative_expiry " , * invoice - > invoice_relative_expiry ) ;
2021-01-07 19:48:47 +01:00
else
2022-11-09 03:32:00 +01:00
json_add_u32 ( js , " invoice_relative_expiry " , BOLT12_DEFAULT_REL_EXPIRY ) ;
2021-01-07 19:48:47 +01:00
2022-11-09 03:32:00 +01:00
if ( invoice - > invoice_payment_hash )
json_add_sha256 ( js , " invoice_payment_hash " , invoice - > invoice_payment_hash ) ;
else {
json_add_string ( js , " warning_missing_invoice_payment_hash " ,
" invoices without a payment_hash are invalid " ) ;
valid = false ;
2022-11-09 03:32:00 +01:00
}
2021-01-07 19:48:47 +01:00
/* BOLT-offers #12:
2022-11-09 03:32:00 +01:00
* - MUST reject the invoice if ` invoice_amount ` is not present .
2022-11-09 03:32:00 +01:00
*/
2022-11-09 03:32:00 +01:00
if ( invoice - > invoice_amount )
2023-03-14 06:21:50 +01:00
json_add_amount_msat ( js , " invoice_amount_msat " ,
amount_msat ( * invoice - > invoice_amount ) ) ;
2021-05-26 07:46:01 +02:00
else {
2022-11-09 03:32:00 +01:00
json_add_string ( js , " warning_missing_invoice_amount " ,
" invoices without an amount are invalid " ) ;
2021-05-26 07:46:01 +02:00
valid = false ;
}
2022-11-09 03:32:00 +01:00
if ( invoice - > invoice_fallbacks )
valid & = json_add_fallbacks ( js ,
invoice - > invreq_chain ,
invoice - > invoice_fallbacks ) ;
if ( invoice - > invoice_features )
json_add_hex_talarr ( js , " features " , invoice - > invoice_features ) ;
if ( invoice - > invoice_node_id )
json_add_pubkey ( js , " invoice_node_id " , invoice - > invoice_node_id ) ;
2022-11-09 03:32:00 +01:00
else {
2022-11-09 03:32:00 +01:00
json_add_string ( js , " warning_missing_invoice_node_id " ,
" invoices without an invoice_node_id are invalid " ) ;
2022-11-09 03:32:00 +01:00
valid = false ;
}
2022-11-09 03:32:00 +01:00
/* BOLT-offers-recurrence #12:
* - if the offer contained ` recurrence ` :
* - MUST reject the invoice if ` recurrence_basetime ` is not
* set .
2021-01-07 19:48:47 +01:00
*/
2022-11-09 03:32:00 +01:00
if ( invoice - > offer_recurrence ) {
if ( invoice - > invoice_recurrence_basetime )
json_add_u64 ( js , " invoice_recurrence_basetime " ,
* invoice - > invoice_recurrence_basetime ) ;
else {
json_add_string ( js , " warning_missing_invoice_recurrence_basetime " ,
" recurring invoices without a recurrence_basetime are invalid " ) ;
2022-09-10 04:06:31 +02:00
valid = false ;
2021-07-02 02:11:35 +02:00
}
2021-01-07 19:48:47 +01:00
}
2021-05-26 07:46:01 +02:00
2022-11-09 03:32:00 +01:00
/* invoice_decode checked this */
json_add_bip340sig ( js , " signature " , invoice - > signature ) ;
2022-11-09 03:32:01 +01:00
json_add_extra_fields ( js , " unknown_invoice_tlvs " , invoice - > fields ) ;
2021-05-26 07:46:01 +02:00
json_add_bool ( js , " valid " , valid ) ;
2021-01-07 19:48:47 +01:00
}
2022-07-16 15:18:27 +02:00
static void json_add_rune ( struct command * cmd , struct json_stream * js , const struct rune * rune )
{
2022-07-25 03:23:30 +02:00
const char * string ;
/* Simplest to check everything for UTF-8 compliance at once.
* Since separators are | and & ( which cannot appear inside
* UTF - 8 multichars ) , if the entire thing is valid UTF - 8 then
* each part is . */
string = rune_to_string ( tmpctx , rune ) ;
if ( ! utf8_check ( string , strlen ( string ) ) ) {
json_add_hex ( js , " hex " , string , strlen ( string ) ) ;
json_add_string ( js , " warning_rune_invalid_utf8 " ,
" Rune contains invalid UTF-8 strings " ) ;
json_add_bool ( js , " valid " , false ) ;
return ;
}
2022-07-16 15:18:27 +02:00
if ( rune - > unique_id )
json_add_string ( js , " unique_id " , rune - > unique_id ) ;
if ( rune - > version )
json_add_string ( js , " version " , rune - > version ) ;
2022-07-25 03:23:30 +02:00
json_add_string ( js , " string " , take ( string ) ) ;
2022-07-16 15:18:27 +02:00
json_array_start ( js , " restrictions " ) ;
for ( size_t i = rune - > unique_id ? 1 : 0 ; i < tal_count ( rune - > restrs ) ; i + + ) {
const struct rune_restr * restr = rune - > restrs [ i ] ;
char * summary = tal_strdup ( tmpctx , " " ) ;
const char * sep = " " ;
json_object_start ( js , NULL ) ;
json_array_start ( js , " alternatives " ) ;
for ( size_t j = 0 ; j < tal_count ( restr - > alterns ) ; j + + ) {
const struct rune_altern * alt = restr - > alterns [ j ] ;
const char * annotation , * value ;
bool int_val = false , time_val = false ;
if ( streq ( alt - > fieldname , " time " ) ) {
annotation = " in seconds since 1970 " ;
time_val = true ;
} else if ( streq ( alt - > fieldname , " id " ) )
annotation = " of commanding peer " ;
else if ( streq ( alt - > fieldname , " method " ) )
annotation = " of command " ;
else if ( streq ( alt - > fieldname , " pnum " ) ) {
annotation = " number of command parameters " ;
int_val = true ;
} else if ( streq ( alt - > fieldname , " rate " ) ) {
annotation = " max per minute " ;
int_val = true ;
} else if ( strstarts ( alt - > fieldname , " parr " ) ) {
annotation = tal_fmt ( tmpctx , " array parameter #%s " , alt - > fieldname + 4 ) ;
} else if ( strstarts ( alt - > fieldname , " pname " ) )
annotation = tal_fmt ( tmpctx , " object parameter '%s' " , alt - > fieldname + 5 ) ;
else
annotation = " unknown condition? " ;
tal_append_fmt ( & summary , " %s " , sep ) ;
/* Where it's ambiguous, quote if it's not treated as an int */
if ( int_val )
value = alt - > value ;
else if ( time_val ) {
u64 t = atol ( alt - > value ) ;
if ( t ) {
u64 diff , now = time_now ( ) . ts . tv_sec ;
/* Need a non-const during construction */
char * v ;
if ( now > t )
diff = now - t ;
else
diff = t - now ;
if ( diff < 60 )
v = tal_fmt ( tmpctx , " % " PRIu64 " seconds " , diff ) ;
else if ( diff < 60 * 60 )
v = tal_fmt ( tmpctx , " % " PRIu64 " minutes % " PRIu64 " seconds " ,
diff / 60 , diff % 60 ) ;
else {
v = tal_strdup ( tmpctx , " approximately " ) ;
/* diff is in minutes */
diff / = 60 ;
if ( diff < 48 * 60 )
tal_append_fmt ( & v , " % " PRIu64 " hours % " PRIu64 " minutes " ,
diff / 60 , diff % 60 ) ;
else {
/* hours */
diff / = 60 ;
if ( diff < 60 * 24 )
tal_append_fmt ( & v , " % " PRIu64 " days % " PRIu64 " hours " ,
diff / 24 , diff % 24 ) ;
else {
/* days */
diff / = 24 ;
if ( diff < 365 * 2 )
tal_append_fmt ( & v , " % " PRIu64 " months % " PRIu64 " days " ,
diff / 30 , diff % 30 ) ;
else {
/* months */
diff / = 30 ;
tal_append_fmt ( & v , " % " PRIu64 " years % " PRIu64 " months " ,
diff / 12 , diff % 12 ) ;
}
}
}
}
if ( now > t )
tal_append_fmt ( & v , " ago " ) ;
else
tal_append_fmt ( & v , " from now " ) ;
value = tal_fmt ( tmpctx , " %s (%s) " , alt - > value , v ) ;
} else
value = alt - > value ;
} else
value = tal_fmt ( tmpctx , " '%s' " , alt - > value ) ;
switch ( alt - > condition ) {
case RUNE_COND_IF_MISSING :
tal_append_fmt ( & summary , " %s (%s) is missing " , alt - > fieldname , annotation ) ;
break ;
case RUNE_COND_EQUAL :
tal_append_fmt ( & summary , " %s (%s) equal to %s " , alt - > fieldname , annotation , value ) ;
break ;
case RUNE_COND_NOT_EQUAL :
tal_append_fmt ( & summary , " %s (%s) unequal to %s " , alt - > fieldname , annotation , value ) ;
break ;
case RUNE_COND_BEGINS :
tal_append_fmt ( & summary , " %s (%s) starts with '%s' " , alt - > fieldname , annotation , alt - > value ) ;
break ;
case RUNE_COND_ENDS :
tal_append_fmt ( & summary , " %s (%s) ends with '%s' " , alt - > fieldname , annotation , alt - > value ) ;
break ;
case RUNE_COND_CONTAINS :
tal_append_fmt ( & summary , " %s (%s) contains '%s' " , alt - > fieldname , annotation , alt - > value ) ;
break ;
case RUNE_COND_INT_LESS :
tal_append_fmt ( & summary , " %s (%s) less than %s " , alt - > fieldname , annotation ,
time_val ? value : alt - > value ) ;
break ;
case RUNE_COND_INT_GREATER :
tal_append_fmt ( & summary , " %s (%s) greater than %s " , alt - > fieldname , annotation ,
time_val ? value : alt - > value ) ;
break ;
case RUNE_COND_LEXO_BEFORE :
tal_append_fmt ( & summary , " %s (%s) sorts before '%s' " , alt - > fieldname , annotation , alt - > value ) ;
break ;
case RUNE_COND_LEXO_AFTER :
tal_append_fmt ( & summary , " %s (%s) sorts after '%s' " , alt - > fieldname , annotation , alt - > value ) ;
break ;
case RUNE_COND_COMMENT :
tal_append_fmt ( & summary , " [comment: %s%s] " , alt - > fieldname , alt - > value ) ;
break ;
}
sep = " OR " ;
json_add_str_fmt ( js , NULL , " %s%c%s " , alt - > fieldname , alt - > condition , alt - > value ) ;
}
json_array_end ( js ) ;
json_add_string ( js , " summary " , summary ) ;
json_object_end ( js ) ;
}
json_array_end ( js ) ;
/* FIXME: do some sanity checks? */
json_add_bool ( js , " valid " , true ) ;
}
2023-10-26 05:12:13 +02:00
static struct command_result * after_makesecret ( struct command * cmd ,
const char * buf ,
const jsmntok_t * result ,
struct decodable * decodable )
{
struct secret secret ;
struct json_stream * response ;
const jsmntok_t * secrettok ;
secrettok = json_get_member ( buf , result , " secret " ) ;
json_to_secret ( buf , secrettok , & secret ) ;
crypto_secretstream_xchacha20poly1305_state crypto_state ;
if ( tal_bytelen ( decodable - > emergency_recover ) < ABYTES +
HEADER_LEN )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" Can't decrypt, hex is too short! " ) ;
u8 * decrypt_blob = tal_arr ( tmpctx , u8 ,
tal_bytelen ( decodable - > emergency_recover ) -
ABYTES -
HEADER_LEN ) ;
/* The header part */
if ( crypto_secretstream_xchacha20poly1305_init_pull ( & crypto_state ,
decodable - > emergency_recover ,
secret . data ) ! = 0 ) {
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS , " Can't decrypt! " ) ;
}
if ( crypto_secretstream_xchacha20poly1305_pull ( & crypto_state , decrypt_blob ,
NULL , 0 ,
decodable - > emergency_recover +
HEADER_LEN ,
tal_bytelen ( decodable - > emergency_recover ) -
HEADER_LEN ,
NULL , 0 ) ! = 0 ) {
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS , " Can't decrypt! " ) ;
}
response = jsonrpc_stream_success ( cmd ) ;
json_add_bool ( response , " valid " , true ) ;
json_add_string ( response , " type " , decodable - > type ) ;
json_add_hex ( response , " decrypted " , decrypt_blob ,
tal_bytelen ( decrypt_blob ) ) ;
return command_finished ( cmd , response ) ;
}
2021-01-07 19:48:47 +01:00
static struct command_result * json_decode ( struct command * cmd ,
const char * buffer ,
const jsmntok_t * params )
{
struct decodable * decodable = talz ( cmd , struct decodable ) ;
struct json_stream * response ;
if ( ! param ( cmd , buffer , params ,
p_req ( " string " , param_decodable , decodable ) ,
NULL ) )
return command_param_failed ( ) ;
response = jsonrpc_stream_success ( cmd ) ;
json_add_string ( response , " type " , decodable - > type ) ;
if ( decodable - > offer )
json_add_offer ( response , decodable - > offer ) ;
if ( decodable - > invreq )
json_add_invoice_request ( response , decodable - > invreq ) ;
if ( decodable - > invoice )
json_add_b12_invoice ( response , decodable - > invoice ) ;
2021-05-26 07:46:01 +02:00
if ( decodable - > b11 ) {
/* The bolt11 decoder simply refuses to decode bad invs. */
2021-01-07 19:48:47 +01:00
json_add_bolt11 ( response , decodable - > b11 ) ;
2021-05-26 07:46:01 +02:00
json_add_bool ( response , " valid " , true ) ;
}
2022-07-16 15:18:27 +02:00
if ( decodable - > rune )
json_add_rune ( cmd , response , decodable - > rune ) ;
2023-10-26 05:12:13 +02:00
if ( decodable - > emergency_recover ) {
struct out_req * req ;
req = jsonrpc_request_start ( cmd - > plugin , cmd , " makesecret " ,
after_makesecret , & forward_error ,
decodable ) ;
json_add_string ( req - > js , " string " , " scb secret " ) ;
return send_outreq ( cmd - > plugin , req ) ;
}
2021-01-07 19:48:47 +01:00
return command_finished ( cmd , response ) ;
}
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:13:23 +01:00
{
2021-01-06 06:41:20 +01:00
rpc_scan ( p , " getinfo " ,
take ( json_out_obj ( NULL , NULL , NULL ) ) ,
2024-05-14 13:41:10 +02:00
" {id:%} " , JSON_SCAN ( json_to_pubkey , & id ) ) ;
rpc_scan ( p , " getchaininfo " ,
offer: fix type to the getchaininfo rpc
This is violate what the docs of getchainfo is telling us that the
last_block_height is a u64.
```
2024-05-17T09:53:23.464Z **BROKEN** plugin-offers: Got error reply to getchaininfo: '{\"error\":{\"code\":-1,\"data\":null,\"message\":\"invalid type: string \\\"0\\\", expected u64\"},\"id\":\"init/offers:getchaininfo#1\",\"jsonrpc\":\"2.0\"}\n\n'
2024-05-17T09:53:23.496Z INFO plugin-offers: Killing plugin: exited before replying to init
2024-05-17T09:53:23.496Z **BROKEN** plugin-offers: Plugin marked as important, shutting down lightningd!
2024-05-17T09:53:23.496Z DEBUG lightningd: io_break: lightningd_exit
2024-05-17T09:53:23.496Z **BROKEN** plugin-topology: Reading JSON input: Connection reset by peer
2024-05-17T09:53:23.504Z INFO plugin-topology: Killing plugin: exited before replying to init
2024-05-17T09:53:23.504Z **BROKEN** plugin-topology: Plugin marked as important, shutting down lightningd!
2024-05-17T09:53:23.504Z DEBUG lightningd: io_break: lightningd_exit
2024-05-17T09:53:23.504Z DEBUG plugin-bookkeeper: Setting up database at sqlite3://accounts.sqlite3
2024-05-17T09:53:23.504Z DEBUG connectd: REPLY WIRE_CONNECTD_START_SHUTDOWN_REPLY with 0 fds
2024-05-17T09:53:23.504Z DEBUG lightningd: io_break: connectd_start_shutdown_reply
```
Fixing the following crash
```
Got error reply to getchaininfo: '{"error":{"code":-1,"data":null,"message":"invalid type: string \"0\", expected u64"},"id":"init/offers:getchaininfo#1","jsonrpc":"2.0"}
'lightningd: lightningd already running? Error locking PID file: Resource temporarily unavailable
```
Fixes: 847208f5d8
Changelog-None: offer: fix type to the getchaininfo rpc
Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
2024-05-17 13:49:28 +02:00
take ( json_out_obj ( NULL , " last_height " , NULL ) ) ,
2024-05-14 13:41:10 +02:00
" {headercount:%} " , JSON_SCAN ( json_to_u32 , & blockheight ) ) ;
2020-12-16 04:13:32 +01:00
2021-01-06 06:41:20 +01:00
rpc_scan ( p , " listconfigs " ,
2021-01-13 09:58:38 +01:00
take ( json_out_obj ( NULL , NULL , NULL ) ) ,
2023-06-02 04:36:04 +02:00
" {configs: "
" {cltv-final:{value_int:%}, "
2024-07-17 05:23:00 +02:00
" experimental-offers:{set:%}}} " ,
2022-03-22 09:50:13 +01:00
JSON_SCAN ( json_to_u16 , & cltv_final ) ,
2024-07-17 05:23:00 +02:00
JSON_SCAN ( json_to_bool , & offers_enabled ) ) ;
2021-01-13 04:00:24 +01:00
2022-11-09 03:32:00 +01:00
rpc_scan ( p , " makesecret " ,
take ( json_out_obj ( NULL , " string " , INVOICE_PATH_BASE_STRING ) ) ,
" {secret:%} " ,
JSON_SCAN ( json_to_secret , & invoicesecret_base ) ) ;
2024-07-17 05:23:25 +02:00
rpc_scan ( p , " makesecret " ,
take ( json_out_obj ( NULL , " string " , " offer-blinded-path " ) ) ,
" {secret:%} " ,
JSON_SCAN ( json_to_secret , & offerblinding_base ) ) ;
2021-01-13 04:00:24 +01:00
return NULL ;
2020-12-16 04:13:23 +01:00
}
static const struct plugin_command commands [ ] = {
2020-12-16 04:13:28 +01:00
{
" offer " ,
" payment " ,
2021-01-07 19:46:47 +01:00
" Create an offer to accept money " ,
2021-10-08 02:52:34 +02:00
" Create an offer for invoices of {amount} with {description}, optional {issuer}, internal {label}, {quantity_min}, {quantity_max}, {absolute_expiry}, {recurrence}, {recurrence_base}, {recurrence_paywindow}, {recurrence_limit} and {single_use} " ,
2020-12-16 04:13:28 +01:00
json_offer
} ,
2022-11-09 03:32:01 +01:00
{
" invoicerequest " ,
" payment " ,
" Create an invoice_request to send money " ,
" Create an invoice_request to pay invoices of {amount} with {description}, optional {issuer}, internal {label}, and {absolute_expiry} " ,
json_invoicerequest
} ,
2021-01-07 19:48:47 +01:00
{
" decode " ,
" utility " ,
" Decode {string} message, returning {type} and information. " ,
NULL ,
json_decode ,
} ,
2024-07-17 05:23:00 +02:00
{
" 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 {invreq}, with {label}, optional {amount_msat}, and {timeout} (default 90 seconds). " ,
NULL ,
json_sendinvoice ,
} ,
{
" dev-rawrequest " ,
" util " ,
" Send {invreq} to {nodeid}, wait {timeout} (60 seconds by default) " ,
NULL ,
json_dev_rawrequest ,
. dev_only = true ,
} ,
2020-12-16 04:13:23 +01:00
} ;
2024-07-17 05:23:00 +02:00
2020-12-16 04:13:23 +01:00
int main ( int argc , char * argv [ ] )
{
setup_locale ( ) ;
2020-12-16 04:13:32 +01:00
/* We deal in UTC; mktime() uses local time */
setenv ( " TZ " , " " , 1 ) ;
2022-11-09 03:31:59 +01:00
plugin_main ( argv , init , PLUGIN_RESTARTABLE , true , NULL ,
commands , ARRAY_SIZE ( commands ) ,
notifications , ARRAY_SIZE ( notifications ) ,
hooks , ARRAY_SIZE ( hooks ) ,
2024-07-17 05:23:00 +02:00
NULL , 0 ,
plugin_option ( " fetchinvoice-noconnect " , " flag " ,
" Don't try to connect directly to fetch/pay an invoice. " ,
flag_option , flag_jsonfmt , & disable_connect ) ,
NULL ) ;
2020-12-16 04:13:23 +01:00
}