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>
This commit is contained in:
parent
84dc943cf5
commit
c08ff167b2
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
|
@ -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>
|
||||
|
|
@ -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
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…
Reference in New Issue