Browse Source

decode: new generic API to decode bolt11 and bolt12.

This is experimental for now, but can eventually deprecated
'decodepay' and even decode other kinds of messages.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
ppa
Rusty Russell 4 years ago
parent
commit
c08ff167b2
  1. 26
      common/bolt12.c
  2. 8
      common/bolt12.h
  3. 17
      common/json_helpers.c
  4. 12
      common/json_helpers.h
  5. 1
      doc/Makefile
  6. 1
      doc/index.rst
  7. 172
      doc/lightning-decode.7
  8. 135
      doc/lightning-decode.7.md
  9. 2
      plugins/Makefile
  10. 561
      plugins/offers.c

26
common/bolt12.c

@ -71,6 +71,22 @@ static char *check_features_and_chain(const tal_t *ctx,
return NULL;
}
bool bolt12_check_signature(const struct tlv_field *fields,
const char *messagename,
const char *fieldname,
const struct pubkey32 *key,
const struct bip340sig *sig)
{
struct sha256 m, shash;
merkle_tlv(fields, &m);
sighash_from_merkle(messagename, fieldname, &m, &shash);
return secp256k1_schnorrsig_verify(secp256k1_ctx,
sig->u8,
shash.u.u8,
&key->pubkey) == 1;
}
static char *check_signature(const tal_t *ctx,
const struct tlv_field *fields,
const char *messagename,
@ -78,19 +94,13 @@ static char *check_signature(const tal_t *ctx,
const struct pubkey32 *node_id,
const struct bip340sig *sig)
{
struct sha256 m, shash;
if (!node_id)
return tal_fmt(ctx, "Missing node_id");
if (!sig)
return tal_fmt(ctx, "Missing signature");
merkle_tlv(fields, &m);
sighash_from_merkle(messagename, fieldname, &m, &shash);
if (secp256k1_schnorrsig_verify(secp256k1_ctx,
sig->u8,
shash.u.u8,
&node_id->pubkey) != 1)
if (!bolt12_check_signature(fields,
messagename, fieldname, node_id, sig))
return tal_fmt(ctx, "Invalid signature");
return NULL;
}

8
common/bolt12.h

@ -95,6 +95,14 @@ struct tlv_invoice *invoice_decode_nosig(const tal_t *ctx,
const struct chainparams *must_be_chain,
char **fail);
/* Check a bolt12-style signature. */
bool bolt12_check_signature(const struct tlv_field *fields,
const char *messagename,
const char *fieldname,
const struct pubkey32 *key,
const struct bip340sig *sig)
NO_NULL_ARGS;
/* Given a tal_arr of chains, does it contain this chain? */
bool bolt12_chains_match(const struct bitcoin_blkid *chains,
const struct chainparams *must_be_chain);

17
common/json_helpers.c

@ -170,6 +170,23 @@ void json_add_pubkey(struct json_stream *response,
json_add_hex(response, fieldname, der, sizeof(der));
}
void json_add_pubkey32(struct json_stream *response,
const char *fieldname,
const struct pubkey32 *key)
{
u8 output[32];
secp256k1_xonly_pubkey_serialize(secp256k1_ctx, output, &key->pubkey);
json_add_hex(response, fieldname, output, sizeof(output));
}
void json_add_bip340sig(struct json_stream *response,
const char *fieldname,
const struct bip340sig *sig)
{
json_add_hex(response, fieldname, sig->u8, sizeof(sig->u8));
}
void json_add_txid(struct json_stream *result, const char *fieldname,
const struct bitcoin_txid *txid)
{

12
common/json_helpers.h

@ -9,10 +9,12 @@
struct amount_msat;
struct amount_sat;
struct bip340sig;
struct channel_id;
struct node_id;
struct preimage;
struct pubkey;
struct pubkey32;
struct secret;
struct short_channel_id;
struct wireaddr;
@ -83,6 +85,16 @@ void json_add_pubkey(struct json_stream *response,
const char *fieldname,
const struct pubkey *key);
/* '"fieldname" : "89abcdef..."' or "89abcdef..." if fieldname is NULL */
void json_add_pubkey32(struct json_stream *response,
const char *fieldname,
const struct pubkey32 *key);
/* '"fieldname" : "89abcdef..."' or "89abcdef..." if fieldname is NULL */
void json_add_bip340sig(struct json_stream *response,
const char *fieldname,
const struct bip340sig *sig);
/* '"fieldname" : "89abcdef..."' or "89abcdef..." if fieldname is NULL */
void json_add_secret(struct json_stream *response,
const char *fieldname,

1
doc/Makefile

@ -15,6 +15,7 @@ MANPAGES := doc/lightning-cli.1 \
doc/lightning-createonion.7 \
doc/lightning-createinvoice.7 \
doc/lightning-decodepay.7 \
doc/lightning-decode.7 \
doc/lightning-delexpiredinvoice.7 \
doc/lightning-delinvoice.7 \
doc/lightning-delpay.7 \

1
doc/index.rst

@ -37,6 +37,7 @@ c-lightning Documentation
lightning-connect <lightning-connect.7.md>
lightning-createinvoice <lightning-createinvoice.7.md>
lightning-createonion <lightning-createonion.7.md>
lightning-decode <lightning-decode.7.md>
lightning-decodepay <lightning-decodepay.7.md>
lightning-delexpiredinvoice <lightning-delexpiredinvoice.7.md>
lightning-delinvoice <lightning-delinvoice.7.md>

172
doc/lightning-decode.7

@ -0,0 +1,172 @@
.TH "LIGHTNING-DECODE" "7" "" "" "lightning-decode"
.SH NAME
lightning-decode - Command for decoding an invoice string (low-level)
.SH SYNOPSIS
\fIEXPERIMENTAL_FEATURES only\fR
\fBdecode\fR \fIstring\fR
.SH DESCRIPTION
The \fBdecode\fR RPC command checks and parses a \fIbolt11\fR or \fIbolt12\fR
string (optionally prefixed by \fBlightning:\fR or \fBLIGHTNING:\fR) as
specified by the BOLT 11 and BOLT 12 specifications\. It may decode
other formats in future\.
.SH RETURN VALUE
On success, an object is returned with a \fItype\fR member indicating the
type of the decoding:
\fItype\fR: "bolt12 offer"
.nf
.RS
- *offer_id*: the id of this offer (merkle hash of non-signature fields)
- *chains* (optional): if set, an array of genesis hashes of supported chains. (Unset implies bitcoin mainnet).
- *currency* (optional): ISO 4217 code of the currency.
- *minor_unit* (optional): the number of decimal places to apply to amount (if currency known)
- *amount* (optional): the amount in the *currency* adjusted by *minor_unit*, if any.
- *amount_msat* (optional): the amount (with "msat" appended) if there is no *currency*.
- *send_invoice* (optional): `true` if this is a send_invoice offer.
- *refund_for* (optional): the sha256 payment_preimage of invoice this is a refund for.
- *description* (optional): the UTF-8 description of the purpose of the offer.
- *vendor* (optional): the UTF-8 name of the vendor for this offer.
- *features* (optional): hex array of feature bits.
- *absolute_expiry* (optional): UNIX timestamp of when this offer expires.
- *paths* (optional): Array of objects containing *blinding*, *path* array; each *path* entry contains an object with *node_id* and *enctlv*.
- *quantity_min* (optional): minimum valid quantity for offer responses
- *quantity_max* (optional): maximum valid quantity for offer responses
- *recurrence* (optional): an object containing *time_unit*, *time_unit_name* (optional, a string), *period*, *basetime* (optional), *start_any_period* (optional), *limit* (optional), and *paywindow* (optional) object containing *seconds_before*, *seconds_after* and *proportional_amount* (optional).
- *node_id*: 32-byte (x-only) public key of the offering node.
- *signature*: BIP-340 signature of the *node_id* on this offer.
.RE
.fi
\fItype\fR: "bolt12 invoice"
.nf
.RS
- *chains* (optional): if set, an array of genesis hashes of supported chains. (Unset implies bitcoin mainnet).
- *offer_id* (optional): id of the offer this invoice is for.
- *amount_msat* (optional): the amount (with "msat" appended).
- *description* (optional): the UTF-8 description of the purpose of the offer.
- *vendor* (optional): the UTF-8 name of the vendor for this offer.
- *features* (optional): hex array of feature bits.
- *paths* (optional): Array of objects containing *blinding*, *path* array; each *path* entry contains an object with *node_id*, *enctlv*, *fee_base_msat* (optional), *fee_proportional_millionths* (optional), *cltv_expiry_delta* (optional), and *features* (optional).
- *quantity* (optional): quantity of items.
- *send_invoice* (optional): `true` if this is a response to a send_invoice offer.
- *refund_for* (optional): the sha256 payment_preimage of invoice this is a refund for.
- *recurrence_counter* (optional): the zero-based number of the invoice for a recurring offer.
- *recurrence_start* (optional): the zero-based offet of the first invoice for the recurring offer.
- *recurrence_basetime* (optional): the UNIX timestamp of the first period of the offer.
- *payer_key* (optional): the 32-byte (x-only) id of the payer.
- *payer_info* (optional): a variable-length blob for the payer to derive their key.
- *timestamp* (optional): the UNIX timestamp of the invoice.
- *payment_hash* (optional): the hex SHA256 of the payment_preimage.
- *expiry* (optional): seconds from *timestamp* when invoice expires.
- *min_final_cltv_expiry*: required CLTV for final hop.
- *fallbacks* (optional): an array containing objects with *version*, and *hex* fields for each fallback address, and *address* (optional) if it's parsable.
- *refund_signature* (optional): BIP-340 signature of the *payer_key* on this offer.
- *node_id*: 32-byte (x-only) public key of the invoicing node.
- *signature*: BIP-340 signature of the *node_id* on this invoice.
.RE
.fi
\fItype\fR: "bolt12 invoice_request"
.nf
.RS
- *chains* (optional): if set, an array of genesis hashes of supported chains. (Unset implies bitcoin mainnet).
- *offer_id* (optional): id of the offer this invoice is for.
- *amount_msat* (optional): the amount (with "msat" appended).
- *features* (optional): hex array of feature bits.
- *quantity* (optional): quantity of items.
- *recurrence_counter* (optional): the zero-based number of the invoice for a recurring offer.
- *recurrence_start* (optional): the zero-based offet of the first invoice for the recurring offer.
- *payer_key* (optional): the 32-byte (x-only) id of the payer.
- *payer_info* (optional): a variable-length blob for the payer to derive their key.
- *recurrence_signature* (optional): BIP-340 signature of the *payer_key* on this offer.
.RE
.fi
\fItype\fR: "bolt11 invoice"
.nf
.RS
- *currency*: the BIP173 name for the currency.
- *timestamp*: the UNIX-style timestamp of the invoice.
- *expiry*: the number of seconds this is valid after *timestamp*.
- *payee*: the public key of the recipient.
- *payment_hash*: the payment hash of the request.
- *signature*: the DER-encoded signature.
- *description*: the UTF-8 description of the purpose of the purchase.
- *msatoshi* (optional): the number of millisatoshi requested (if any).
- *amount_msat* (optional): the same as above, with *msat* appended (if any).
- *fallbacks* (optional): array of fallback address object containing a *hex* string, and both *type* and *addr* if it is recognized as one of *P2PKH*, *P2SH*, *P2WPKH*, or *P2WSH*.
- *routes* (optional): an array of routes. Each route is an arrays of objects, each containing *pubkey*, *short_channel_id*, *fee_base_msat*, *fee_proportional_millionths* and *cltv_expiry_delta*.
- *extra* (optional): an array of objects representing unknown fields, each with one-character *tag* and a *data* bech32 string.
.RE
.fi
Some invalid strings can still be parsed, and warnings will be given:
.nf
.RS
- "warning_offer_unknown_currency": unknown or invalid *currency* code.
- "warning_offer_missing_description": invalid due to missing description.
- "warning_invoice_invalid_blinded_payinfo": blinded_payinfo does not match paths.
- "warning_invoice_fallbacks_version_invalid": a fallback version is not a valid segwit version
- "warning_invoice_fallbacks_address_invalid": a fallback address is not a valid segwit address (within an object in the *fallback* array)
- "warning_invoice_missing_amount": amount field is missing.
- "warning_invoice_missing_description": description field is missing.
- "warning_invoice_missing_blinded_payinfo": blindedpay is missing.
- "warning_invoice_missing_recurrence_basetime: recurrence_basetime is missing.
- "warning_invoice_missing_timestamp": timestamp is missing.
- "warning_invoice_missing_payment_hash": payment hash is missing.
- "warning_invoice_refund_signature_missing_payer_key": payer_key is missing for refund_signature.
- "warning_invoice_refund_signature_invalid": refund_signature does not match.
- "warning_invoice_refund_missing_signature": refund_signature is missing.
- "warning_invoice_request_missing_offer_id": offer_id is missing.
- "warning_invoice_request_missing_payer_key": payer_key is missing.
- "warning_invoice_request_invalid_recurrence_signature": recurrence_signature does not match.
- "warning_invoice_request_missing_recurrence_signature": recurrence_signature is missing.
.RE
.fi
.SH AUTHOR
Rusty Russell \fI<rusty@rustcorp.com.au\fR> is mainly responsible\.
.SH SEE ALSO
\fBlightning-pay\fR(7), \fBlightning-offer\fR(7), \fBlightning-offerout\fR(7), \fBlightning-fetchinvoice\fR(7), \fBlightning-sendinvoice\fR(7)
\fBBOLT #11\fR (\fIhttps://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md\fR)\.
\fBBOLT #12\fR (\fIhttps://github.com/lightningnetwork/lightning-rfc/blob/master/12-offer-encoding.md\fR)\.
.SH RESOURCES
Main web site: \fIhttps://github.com/ElementsProject/lightning\fR
\" SHA256STAMP:6920ea3b5e3fe8c193ce149b813496370fbc249649911595ea857f5cfb7d6e89

135
doc/lightning-decode.7.md

@ -0,0 +1,135 @@
lightning-decode -- Command for decoding an invoice string (low-level)
=======================================================================
SYNOPSIS
--------
*EXPERIMENTAL_FEATURES only*
**decode** *string*
DESCRIPTION
-----------
The **decode** RPC command checks and parses a *bolt11* or *bolt12*
string (optionally prefixed by `lightning:` or `LIGHTNING:`) as
specified by the BOLT 11 and BOLT 12 specifications. It may decode
other formats in future.
RETURN VALUE
------------
On success, an object is returned with a *type* member indicating the
type of the decoding:
*type*: "bolt12 offer"
- *offer_id*: the id of this offer (merkle hash of non-signature fields)
- *chains* (optional): if set, an array of genesis hashes of supported chains. (Unset implies bitcoin mainnet).
- *currency* (optional): ISO 4217 code of the currency.
- *minor_unit* (optional): the number of decimal places to apply to amount (if currency known)
- *amount* (optional): the amount in the *currency* adjusted by *minor_unit*, if any.
- *amount_msat* (optional): the amount (with "msat" appended) if there is no *currency*.
- *send_invoice* (optional): `true` if this is a send_invoice offer.
- *refund_for* (optional): the sha256 payment_preimage of invoice this is a refund for.
- *description* (optional): the UTF-8 description of the purpose of the offer.
- *vendor* (optional): the UTF-8 name of the vendor for this offer.
- *features* (optional): hex array of feature bits.
- *absolute_expiry* (optional): UNIX timestamp of when this offer expires.
- *paths* (optional): Array of objects containing *blinding*, *path* array; each *path* entry contains an object with *node_id* and *enctlv*.
- *quantity_min* (optional): minimum valid quantity for offer responses
- *quantity_max* (optional): maximum valid quantity for offer responses
- *recurrence* (optional): an object containing *time_unit*, *time_unit_name* (optional, a string), *period*, *basetime* (optional), *start_any_period* (optional), *limit* (optional), and *paywindow* (optional) object containing *seconds_before*, *seconds_after* and *proportional_amount* (optional).
- *node_id*: 32-byte (x-only) public key of the offering node.
- *signature*: BIP-340 signature of the *node_id* on this offer.
*type*: "bolt12 invoice"
- *chains* (optional): if set, an array of genesis hashes of supported chains. (Unset implies bitcoin mainnet).
- *offer_id* (optional): id of the offer this invoice is for.
- *amount_msat* (optional): the amount (with "msat" appended).
- *description* (optional): the UTF-8 description of the purpose of the offer.
- *vendor* (optional): the UTF-8 name of the vendor for this offer.
- *features* (optional): hex array of feature bits.
- *paths* (optional): Array of objects containing *blinding*, *path* array; each *path* entry contains an object with *node_id*, *enctlv*, *fee_base_msat* (optional), *fee_proportional_millionths* (optional), *cltv_expiry_delta* (optional), and *features* (optional).
- *quantity* (optional): quantity of items.
- *send_invoice* (optional): `true` if this is a response to a send_invoice offer.
- *refund_for* (optional): the sha256 payment_preimage of invoice this is a refund for.
- *recurrence_counter* (optional): the zero-based number of the invoice for a recurring offer.
- *recurrence_start* (optional): the zero-based offet of the first invoice for the recurring offer.
- *recurrence_basetime* (optional): the UNIX timestamp of the first period of the offer.
- *payer_key* (optional): the 32-byte (x-only) id of the payer.
- *payer_info* (optional): a variable-length blob for the payer to derive their key.
- *timestamp* (optional): the UNIX timestamp of the invoice.
- *payment_hash* (optional): the hex SHA256 of the payment_preimage.
- *expiry* (optional): seconds from *timestamp* when invoice expires.
- *min_final_cltv_expiry*: required CLTV for final hop.
- *fallbacks* (optional): an array containing objects with *version*, and *hex* fields for each fallback address, and *address* (optional) if it's parsable.
- *refund_signature* (optional): BIP-340 signature of the *payer_key* on this offer.
- *node_id*: 32-byte (x-only) public key of the invoicing node.
- *signature*: BIP-340 signature of the *node_id* on this invoice.
*type*: "bolt12 invoice_request"
- *chains* (optional): if set, an array of genesis hashes of supported chains. (Unset implies bitcoin mainnet).
- *offer_id* (optional): id of the offer this invoice is for.
- *amount_msat* (optional): the amount (with "msat" appended).
- *features* (optional): hex array of feature bits.
- *quantity* (optional): quantity of items.
- *recurrence_counter* (optional): the zero-based number of the invoice for a recurring offer.
- *recurrence_start* (optional): the zero-based offet of the first invoice for the recurring offer.
- *payer_key* (optional): the 32-byte (x-only) id of the payer.
- *payer_info* (optional): a variable-length blob for the payer to derive their key.
- *recurrence_signature* (optional): BIP-340 signature of the *payer_key* on this offer.
*type*: "bolt11 invoice"
- *currency*: the BIP173 name for the currency.
- *timestamp*: the UNIX-style timestamp of the invoice.
- *expiry*: the number of seconds this is valid after *timestamp*.
- *payee*: the public key of the recipient.
- *payment_hash*: the payment hash of the request.
- *signature*: the DER-encoded signature.
- *description*: the UTF-8 description of the purpose of the purchase.
- *msatoshi* (optional): the number of millisatoshi requested (if any).
- *amount_msat* (optional): the same as above, with *msat* appended (if any).
- *fallbacks* (optional): array of fallback address object containing a *hex* string, and both *type* and *addr* if it is recognized as one of *P2PKH*, *P2SH*, *P2WPKH*, or *P2WSH*.
- *routes* (optional): an array of routes. Each route is an arrays of objects, each containing *pubkey*, *short_channel_id*, *fee_base_msat*, *fee_proportional_millionths* and *cltv_expiry_delta*.
- *extra* (optional): an array of objects representing unknown fields, each with one-character *tag* and a *data* bech32 string.
Some invalid strings can still be parsed, and warnings will be given:
- "warning_offer_unknown_currency": unknown or invalid *currency* code.
- "warning_offer_missing_description": invalid due to missing description.
- "warning_invoice_invalid_blinded_payinfo": blinded_payinfo does not match paths.
- "warning_invoice_fallbacks_version_invalid": a fallback version is not a valid segwit version
- "warning_invoice_fallbacks_address_invalid": a fallback address is not a valid segwit address (within an object in the *fallback* array)
- "warning_invoice_missing_amount": amount field is missing.
- "warning_invoice_missing_description": description field is missing.
- "warning_invoice_missing_blinded_payinfo": blindedpay is missing.
- "warning_invoice_missing_recurrence_basetime: recurrence_basetime is missing.
- "warning_invoice_missing_timestamp": timestamp is missing.
- "warning_invoice_missing_payment_hash": payment hash is missing.
- "warning_invoice_refund_signature_missing_payer_key": payer_key is missing for refund_signature.
- "warning_invoice_refund_signature_invalid": refund_signature does not match.
- "warning_invoice_refund_missing_signature": refund_signature is missing.
- "warning_invoice_request_missing_offer_id": offer_id is missing.
- "warning_invoice_request_missing_payer_key": payer_key is missing.
- "warning_invoice_request_invalid_recurrence_signature": recurrence_signature does not match.
- "warning_invoice_request_missing_recurrence_signature": recurrence_signature is missing.
AUTHOR
------
Rusty Russell <<rusty@rustcorp.com.au>> is mainly responsible.
SEE ALSO
--------
lightning-pay(7), lightning-offer(7), lightning-offerout(7), lightning-fetchinvoice(7), lightning-sendinvoice(7)
[BOLT \#11](https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md).
[BOLT \#12](https://github.com/lightningnetwork/lightning-rfc/blob/master/12-offer-encoding.md).
RESOURCES
---------
Main web site: <https://github.com/ElementsProject/lightning>

2
plugins/Makefile

@ -141,7 +141,7 @@ $(PLUGIN_KEYSEND_OBJS): $(PLUGIN_PAY_LIB_HEADER)
plugins/spenderp: bitcoin/chainparams.o bitcoin/psbt.o common/psbt_open.o $(PLUGIN_SPENDER_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS)
plugins/offers: bitcoin/chainparams.o $(PLUGIN_OFFERS_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) common/bolt12.o common/bolt12_merkle.o common/iso4217.o wire/bolt12_exp_wiregen.o $(WIRE_OBJS) bitcoin/block.o common/channel_id.o bitcoin/preimage.o $(JSMN_OBJS) $(CCAN_OBJS)
plugins/offers: bitcoin/chainparams.o $(PLUGIN_OFFERS_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) common/bolt12.o common/bolt12_merkle.o common/bolt11_json.o common/iso4217.o wire/bolt12_exp_wiregen.o $(WIRE_OBJS) bitcoin/block.o common/channel_id.o bitcoin/preimage.o $(JSMN_OBJS) $(CCAN_OBJS)
plugins/fetchinvoice: bitcoin/chainparams.o $(PLUGIN_FETCHINVOICE_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) common/bolt12.o common/bolt12_merkle.o common/iso4217.o wire/bolt12_exp_wiregen.o $(WIRE_OBJS) bitcoin/block.o common/channel_id.o bitcoin/preimage.o $(JSMN_OBJS) $(CCAN_OBJS) common/gossmap.o common/dijkstra.o common/route.o common/blindedpath.o common/hmac.o common/blinding.o

561
plugins/offers.c

@ -1,5 +1,13 @@
/* This plugin covers both sending and receiving offers */
#include <bitcoin/chainparams.h>
#include <ccan/array_size/array_size.h>
#include <ccan/tal/str/str.h>
#include <common/bech32.h>
#include <common/bolt11.h>
#include <common/bolt11_json.h>
#include <common/bolt12.h>
#include <common/bolt12_merkle.h>
#include <common/iso4217.h>
#include <common/json_stream.h>
#include <plugins/libplugin.h>
#include <plugins/offers.h>
@ -111,6 +119,552 @@ static const struct plugin_hook hooks[] = {
},
};
struct decodable {
const char *type;
struct bolt11 *b11;
struct tlv_offer *offer;
struct tlv_invoice *invoice;
struct tlv_invoice_request *invreq;
};
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;
}
/* 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;
}
/* 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,
const struct bitcoin_blkid *chains)
{
json_array_start(js, "chains");
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,
const struct onionmsg_path *path,
const struct blinded_payinfo *payinfo)
{
json_object_start(js, fieldname);
json_add_pubkey(js, "node_id", &path->node_id);
json_add_hex_talarr(js, "enctlv", path->enctlv);
if (payinfo) {
json_add_u32(js, "fee_base_msat", payinfo->fee_base_msat);
json_add_u32(js, "fee_proportional_millionths",
payinfo->fee_proportional_millionths);
json_add_u32(js, "cltv_expiry_delta",
payinfo->cltv_expiry_delta);
json_add_hex_talarr(js, "features", payinfo->features);
}
json_object_end(js);
}
static void json_add_blinded_paths(struct json_stream *js,
struct blinded_path **paths,
struct blinded_payinfo **blindedpay)
{
size_t n = 0;
json_array_start(js, "paths");
for (size_t i = 0; i < tal_count(paths); i++) {
json_object_start(js, NULL);
json_add_pubkey(js, "blinding", &paths[i]->blinding);
json_array_start(js, "path");
for (size_t j = 0; j < tal_count(paths[i]->path); j++) {
json_add_onionmsg_path(js, NULL, paths[i]->path[j],
n < tal_count(blindedpay)
? blindedpay[n] : NULL);
n++;
}
json_array_end(js);
json_object_end(js);
}
json_array_end(js);
/* BOLT-offers #12:
* - MUST reject the invoice if `blinded_payinfo` does not contain
* exactly as many `payinfo` as total `onionmsg_path` in
* `blinded_path`.
*/
if (blindedpay && n != tal_count(blindedpay))
json_add_string(js, "warning_invoice_invalid_blinded_payinfo",
"invoice does not have correct number of blinded_payinfo");
}
static const char *recurrence_time_unit_name(u8 time_unit)
{
/* BOLT-offers #12:
* `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;
}
static void json_add_offer(struct json_stream *js, const struct tlv_offer *offer)
{
struct sha256 offer_id;
merkle_tlv(offer->fields, &offer_id);
json_add_sha256(js, "offer_id", &offer_id);
if (offer->chains)
json_add_chains(js, offer->chains);
if (offer->currency) {
const struct iso4217_name_and_divisor *iso4217;
json_add_stringn(js, "currency",
offer->currency, tal_bytelen(offer->currency));
if (offer->amount)
json_add_u64(js, "amount", *offer->amount);
iso4217 = find_iso4217(offer->currency,
tal_bytelen(offer->currency));
if (iso4217)
json_add_num(js, "minor_unit", iso4217->minor_unit);
else
json_add_string(js, "warning_offer_unknown_currency",
"unknown currency code");
} else if (offer->amount)
json_add_amount_msat_only(js, "amount_msat",
amount_msat(*offer->amount));
if (offer->send_invoice)
json_add_bool(js, "send_invoice", true);
if (offer->refund_for)
json_add_sha256(js, "refund_for", offer->refund_for);
/* BOLT-offers #12:
* A reader of an offer:
*...
* - if `node_id`, `description` or `signature` is not set:
* - MUST NOT respond to the offer.
*/
if (offer->description)
json_add_stringn(js, "description",
offer->description,
tal_bytelen(offer->description));
else
json_add_string(js, "warning_offer_missing_description",
"offers without a description are invalid");
if (offer->vendor)
json_add_stringn(js, "vendor", offer->vendor,
tal_bytelen(offer->vendor));
if (offer->features)
json_add_hex_talarr(js, "features", offer->features);
if (offer->absolute_expiry)
json_add_u64(js, "absolute_expiry",
*offer->absolute_expiry);
if (offer->paths)
json_add_blinded_paths(js, offer->paths, NULL);
if (offer->quantity_min)
json_add_u64(js, "quantity_min", *offer->quantity_min);
if (offer->quantity_max)
json_add_u64(js, "quantity_max", *offer->quantity_max);
if (offer->recurrence) {
const char *name;
json_object_start(js, "recurrence");
json_add_num(js, "time_unit", offer->recurrence->time_unit);
name = recurrence_time_unit_name(offer->recurrence->time_unit);
if (name)
json_add_string(js, "time_unit_name", name);
json_add_num(js, "period", offer->recurrence->period);
if (offer->recurrence_base) {
json_add_u64(js, "basetime",
offer->recurrence_base->basetime);
if (offer->recurrence_base->start_any_period)
json_add_bool(js, "start_any_period", true);
}
if (offer->recurrence_limit)
json_add_u32(js, "limit", *offer->recurrence_limit);
if (offer->recurrence_paywindow) {
json_object_start(js, "paywindow");
json_add_u32(js, "seconds_before",
offer->recurrence_paywindow->seconds_before);
json_add_u32(js, "seconds_after",
offer->recurrence_paywindow->seconds_after);
if (offer->recurrence_paywindow->proportional_amount)
json_add_bool(js, "proportional_amount", true);
json_object_end(js);
}
json_object_end(js);
}
/* offer_decode fails if node_id or signature not set */
json_add_pubkey32(js, "node_id", offer->node_id);
json_add_bip340sig(js, "signature", offer->signature);
}
static void json_add_fallback_address(struct json_stream *js,
const struct chainparams *chain,
u8 version, const u8 *address)
{
char out[73 + strlen(chain->bip173_name)];
/* Does extra checks, in particular checks v0 sizes */
if (segwit_addr_encode(out, chain->bip173_name, version,
address, tal_bytelen(address)))
json_add_string(js, "address", out);
else
json_add_string(js,
"warning_invoice_fallbacks_address_invalid",
"invalid fallback address for this version");
}
static void json_add_fallbacks(struct json_stream *js,
const struct bitcoin_blkid *chains,
struct fallback_address **fallbacks)
{
const struct chainparams *chain;
/* Present address as first chain mentioned. */
if (tal_count(chains) != 0)
chain = chainparams_by_chainhash(&chains[0]);
else
chain = chainparams_for_network("bitcoin");
json_array_start(js, "fallbacks");
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:
* - for the bitcoin chain, if the invoice specifies `fallbacks`:
* - 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,
"warning_invoice_fallbacks_version_invalid",
"invoice fallback version > 16");
} else if (addrlen < 2 || addrlen > 40) {
json_add_string(js,
"warning_invoice_fallbacks_address_invalid",
"invoice fallback address bad length");
} else if (chain) {
json_add_fallback_address(js, chain,
fallbacks[i]->version,
fallbacks[i]->address);
}
json_object_end(js);
}
json_array_end(js);
}
static void json_add_b12_invoice(struct json_stream *js,
const struct tlv_invoice *invoice)
{
if (invoice->chains)
json_add_chains(js, invoice->chains);
if (invoice->offer_id)
json_add_sha256(js, "offer_id", invoice->offer_id);
/* BOLT-offers #12:
* - MUST reject the invoice if `msat` is not present.
*/
if (invoice->amount)
json_add_amount_msat_only(js, "amount_msat",
amount_msat(*invoice->amount));
else
json_add_string(js, "warning_invoice_missing_amount",
"invoices without an amount are invalid");
/* BOLT-offers #12:
* - MUST reject the invoice if `description` is not present.
*/
if (invoice->description)
json_add_stringn(js, "description", invoice->description,
tal_bytelen(invoice->description));
else
json_add_string(js, "warning_invoice_missing_description",
"invoices without a description are invalid");
if (invoice->vendor)
json_add_stringn(js, "vendor", invoice->vendor,
tal_bytelen(invoice->vendor));
if (invoice->features)
json_add_hex_talarr(js, "features", invoice->features);
if (invoice->paths) {
/* BOLT-offers #12:
* - if `blinded_path` is present:
* - MUST reject the invoice if `blinded_payinfo` is not
* present.
* - MUST reject the invoice if `blinded_payinfo` does not
* contain exactly as many `payinfo` as total `onionmsg_path`
* in `blinded_path`.
*/
if (!invoice->blindedpay)
json_add_string(js, "warning_invoice_missing_blinded_payinfo",
"invoices with blinded_path without blinded_payindo are invalid");
json_add_blinded_paths(js, invoice->paths, invoice->blindedpay);
}
if (invoice->quantity)
json_add_u64(js, "quantity", *invoice->quantity);
if (invoice->send_invoice)
json_add_bool(js, "send_invoice", true);
if (invoice->refund_for)
json_add_sha256(js, "refund_for", invoice->refund_for);
if (invoice->recurrence_counter) {
json_add_u32(js, "recurrence_counter",
*invoice->recurrence_counter);
if (invoice->recurrence_start)
json_add_u32(js, "recurrence_start",
*invoice->recurrence_start);
/* BOLT-offers #12:
* - if the offer contained `recurrence`:
* - MUST reject the invoice if `recurrence_basetime` is not
* set.
*/
if (invoice->recurrence_basetime)
json_add_u64(js, "recurrence_basetime",
*invoice->recurrence_basetime);
else
json_add_string(js, "warning_invoice_missing_recurrence_basetime",
"recurring invoices without a recurrence_basetime are invalid");
}
if (invoice->payer_key)
json_add_pubkey32(js, "payer_key", invoice->payer_key);
if (invoice->payer_info)
json_add_hex_talarr(js, "payer_info", invoice->payer_info);
/* BOLT-offers #12:
* - MUST reject the invoice if `timestamp` is not present.
*/
if (invoice->timestamp)
json_add_u64(js, "timestamp", *invoice->timestamp);
else
json_add_string(js, "warning_invoice_missing_timestamp",
"invoices without a timestamp are invalid");
/* BOLT-offers #12:
* - MUST reject the invoice if `payment_hash` is not present.
*/
if (invoice->payment_hash)
json_add_sha256(js, "payment_hash", invoice->payment_hash);
else
json_add_string(js, "warning_invoice_missing_payment_hash",
"invoices without a payment_hash are invalid");
/* BOLT-offers #12:
*
* - if the expiry for accepting payment is not 7200 seconds after
* `timestamp`:
* - MUST set `relative_expiry`
*/
if (invoice->relative_expiry)
json_add_u32(js, "relative_expiry", *invoice->relative_expiry);
else
json_add_u32(js, "relative_expiry", 7200);
/* BOLT-offers #12:
* - if the `min_final_cltv_expiry` for the last HTLC in the route is
* not 18:
* - MUST set `min_final_cltv_expiry`.
*/
if (invoice->cltv)
json_add_u32(js, "min_final_cltv_expiry", *invoice->cltv);
else
json_add_u32(js, "min_final_cltv_expiry", 18);
if (invoice->fallbacks)
json_add_fallbacks(js, invoice->chains,
invoice->fallbacks->fallbacks);
/* BOLT-offers #12:
* - if the offer contained `refund_for`:
* - MUST reject the invoice if `payer_key` does not match the invoice
* whose `payment_hash` is equal to `refund_for`
* `refunded_payment_hash`
* - MUST reject the invoice if `refund_signature` is not set.
* - MUST reject the invoice if `refund_signature` is not a valid
* signature using `payer_key` as described in
* [Signature Calculation](#signature-calculation).
*/
if (invoice->refund_signature) {
json_add_bip340sig(js, "refund_signature",
invoice->refund_signature);
if (!invoice->payer_key)
json_add_string(js, "warning_invoice_refund_signature_missing_payer_key",
"Can't have refund_signature without payer key");
else if (!bolt12_check_signature(invoice->fields,
"invoice",
"refund_signature",
invoice->payer_key,
invoice->refund_signature))
json_add_string(js, "warning_invoice_refund_signature_invalid",
"refund_signature does not match");
} else if (invoice->refund_for)
json_add_string(js, "warning_invoice_refund_missing_signature",
"refund_for requires refund_signature");
/* invoice_decode checked these */
json_add_pubkey32(js, "node_id", invoice->node_id);
json_add_bip340sig(js, "signature", invoice->signature);
}
static void json_add_invoice_request(struct json_stream *js,
const struct tlv_invoice_request *invreq)
{
if (invreq->chains)
json_add_chains(js, invreq->chains);
/* BOLT-offers #12:
* - MUST fail the request if `payer_key` is not present.
* - MUST fail the request if `chains` does not include (or imply) a supported chain.
* - MUST fail the request if `features` contains unknown even bits.
* - MUST fail the request if `offer_id` is not present.
*/
if (invreq->offer_id)
json_add_sha256(js, "offer_id", invreq->offer_id);
else
json_add_string(js, "warning_invoice_request_missing_offer_id",
"invoice_request requires offer_id");
if (invreq->amount)
json_add_amount_msat_only(js, "amount_msat",
amount_msat(*invreq->amount));
if (invreq->features)
json_add_hex_talarr(js, "features", invreq->features);
if (invreq->quantity)
json_add_u64(js, "quantity", *invreq->quantity);
if (invreq->recurrence_counter)
json_add_u32(js, "recurrence_counter",
*invreq->recurrence_counter);
if (invreq->recurrence_start)
json_add_u32(js, "recurrence_start",
*invreq->recurrence_start);
if (invreq->payer_key)
json_add_pubkey32(js, "payer_key", invreq->payer_key);
else
json_add_string(js, "warning_invoice_request_missing_payer_key",
"invoice_request requires payer_key");
if (invreq->payer_info)
json_add_hex_talarr(js, "payer_info", invreq->payer_info);
/* BOLT-offers #12:
* - if the offer had a `recurrence`:
* - MUST fail the request if there is no `recurrence_counter` field.
* - MUST fail the request if there is no `recurrence_signature` field.
* - MUST fail the request if `recurrence_signature` is not correct.
*/
if (invreq->recurrence_signature) {
json_add_bip340sig(js, "recurrence_signature",
invreq->recurrence_signature);
if (invreq->payer_key
&& !bolt12_check_signature(invreq->fields,
"invoice_request",
"recurrence_signature",
invreq->payer_key,
invreq->recurrence_signature))
json_add_string(js, "warning_invoice_request_invalid_recurrence_signature",
"Bad recurrence_signature");
} else if (invreq->recurrence_counter) {
json_add_string(js, "warning_invoice_request_missing_recurrence_signature",
"invoice_request requires recurrence_signature");
}
}
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);
if (decodable->b11)
json_add_bolt11(response, decodable->b11);
return command_finished(cmd, response);
}
static void init(struct plugin *p,
const char *buf UNUSED,
const jsmntok_t *config UNUSED)
@ -144,6 +698,13 @@ static const struct plugin_command commands[] = {
"Create an offer to pay invoices of {amount} with {description}, optional {vendor}, internal {label}, {absolute_expiry} and {refund_for}",
json_offerout
},
{
"decode",
"utility",
"Decode {string} message, returning {type} and information.",
NULL,
json_decode,
},
};
int main(int argc, char *argv[])

Loading…
Cancel
Save