Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions common/bolt12.c
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,16 @@ struct tlv_offer *offer_decode(const tal_t *ctx,
return tal_free(offer);
}

/* BOLT #12:
*
* - if `offer_currency` is set and `offer_amount` is not set:
* - MUST NOT respond to the offer.
*/
if (offer->offer_currency && !offer->offer_amount) {
*fail = tal_strdup(ctx, "Offer contains a currency with no amount");
return tal_free(offer);
}

/* BOLT #12:
*
* - if neither `offer_issuer_id` nor `offer_paths` are set:
Expand Down
9 changes: 9 additions & 0 deletions common/utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,15 @@ char *str_lowering(const void *ctx, const char *string TAKES)
return ret;
}

char *str_uppering(const void *ctx, const char *string TAKES)
{
char *ret;

ret = tal_strdup(ctx, string);
for (char *p = ret; *p; p++) *p = toupper(*p);
return ret;
}

/* Realloc helper for tal membufs */
void *membuf_tal_resize(struct membuf *mb, void *rawelems, size_t newsize)
{
Expand Down
9 changes: 9 additions & 0 deletions common/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,15 @@ void *membuf_tal_resize(struct membuf *mb, void *rawelems, size_t newsize);
*/
char *str_lowering(const void *ctx, const char *string TAKES);

/**
* tal_struppering - return the same string by in upper case.
* @ctx: the context to tal from (often NULL)
* @string: the string that is going to be UPPERED (can be take())
*
* FIXME: move this in ccan
*/
char *str_uppering(const void *ctx, const char *string TAKES);

/**
* Assign two different structs which are the same size.
* We use this for assigning secrets <-> sha256 for example.
Expand Down
10 changes: 4 additions & 6 deletions plugins/libplugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -416,14 +416,12 @@ command_success(struct command *cmd, const struct json_out *result)
NON_NULL_ARGS(1, 2);

/* End a hook normally (with "result": "continue") */
struct command_result *WARN_UNUSED_RESULT
command_hook_success(struct command *cmd)
NON_NULL_ARGS(1);
struct command_result *command_hook_success(struct command *cmd)
WARN_UNUSED_RESULT NON_NULL_ARGS(1);

/* End a notification handler. */
struct command_result *WARN_UNUSED_RESULT
notification_handled(struct command *cmd)
NON_NULL_ARGS(1);
struct command_result *notification_handled(struct command *cmd)
WARN_UNUSED_RESULT NON_NULL_ARGS(1);

/* End a command created with aux_command. */
struct command_result *WARN_UNUSED_RESULT
Expand Down
120 changes: 106 additions & 14 deletions plugins/offers.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "config.h"
#include <ccan/array_size/array_size.h>
#include <ccan/bitops/bitops.h>
#include <ccan/mem/mem.h>
#include <ccan/rune/rune.h>
#include <ccan/tal/str/str.h>
#include <common/bech32.h>
Expand Down Expand Up @@ -492,6 +493,91 @@ static u8 *encrypted_decode(const tal_t *ctx, const char *str, char **fail) {
return NULL;
}

enum likely_type {
LIKELY_BOLT12_OFFER,
LIKELY_BOLT12_INV,
LIKELY_BOLT12_INVREQ,
LIKELY_EMERGENCY_RECOVER,
LIKELY_BOLT11,
LIKELY_OTHER,
};

/* Pull, either case! Advances tok->start on success. */
static bool tok_pull(const char *buffer, jsmntok_t *tok, const char *lowerstr)
{
if (!json_tok_startswith(buffer, tok, lowerstr)) {
const char *upperstr = str_uppering(tmpctx, lowerstr);
if (!json_tok_startswith(buffer, tok, upperstr))
return false;
}
tok->start += strlen(lowerstr);
return true;
}

static enum likely_type guess_type(const char *buffer, const jsmntok_t *tok)
{
jsmntok_t tok_copy = *tok;

if (tok_pull(buffer, &tok_copy, "lno1"))
return LIKELY_BOLT12_OFFER;
if (tok_pull(buffer, &tok_copy, "lni1"))
return LIKELY_BOLT12_INV;
if (tok_pull(buffer, &tok_copy, "lnr1"))
return LIKELY_BOLT12_INVREQ;
if (tok_pull(buffer, &tok_copy, "clnemerg1"))
return LIKELY_EMERGENCY_RECOVER;
/* BOLT #11:
*
* The human-readable part of a Lightning invoice consists of
* two sections:
*
* 1. `prefix`: `ln` + BIP-0173 currency prefix (e.g. `lnbc`
* for Bitcoin mainnet, `lntb` for Bitcoin testnet, `lntbs`
* for Bitcoin signet, and `lnbcrt` for Bitcoin regtest)
*
* 1. `amount`: optional number in that currency, followed by
* an optional `multiplier` letter. The unit encoded here
* is the 'social' convention of a payment unit -- in the
* case of Bitcoin the unit is 'bitcoin' NOT satoshis.
*/
if (tok_pull(buffer, &tok_copy, "lnbcrt")
|| tok_pull(buffer, &tok_copy, "lnbc")
|| tok_pull(buffer, &tok_copy, "lntbs")
|| tok_pull(buffer, &tok_copy, "lntb")) {
/* Now find last '1', which separates hrp from data */
const char *delim = memrchr(buffer + tok_copy.start, '1',
tok_copy.end - tok_copy.start);
if (!delim)
return LIKELY_OTHER;

/* BOLT #11:
* The following `multiplier` letters are defined:
*
* * `m` (milli): multiply by 0.001
* * `u` (micro): multiply by 0.000001
* * `n` (nano): multiply by 0.000000001
* * `p` (pico): multiply by 0.000000000001
*/
delim--;
if (delim > buffer + tok_copy.start
&& (tolower(*delim) == 'm'
|| tolower(*delim) == 'u'
|| tolower(*delim) == 'n'
|| tolower(*delim) == 'p')) {
delim--;
}

while (delim >= buffer + tok_copy.start) {
if (!cisdigit(*delim))
return LIKELY_OTHER;
delim--;
}
return LIKELY_BOLT11;
}

return LIKELY_OTHER;
}

static struct command_result *param_decodable(struct command *cmd,
const char *name,
const char *buffer,
Expand All @@ -500,21 +586,22 @@ static struct command_result *param_decodable(struct command *cmd,
{
char *likely_fail = NULL, *fail;
jsmntok_t tok;
enum likely_type type;

/* 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:");
/* Note: either case! */
tok_pull(buffer, &tok, "lightning:");

type = guess_type(buffer, &tok);
decodable->offer = offer_decode(cmd, buffer + tok.start,
tok.end - tok.start,
plugin_feature_set(cmd->plugin), NULL,
json_tok_startswith(buffer, &tok, "lno1")
type == LIKELY_BOLT12_OFFER
? &likely_fail : &fail);
if (decodable->offer) {
decodable->type = "bolt12 offer";
Expand All @@ -525,8 +612,7 @@ static struct command_result *param_decodable(struct command *cmd,
tok.end - tok.start,
plugin_feature_set(cmd->plugin),
NULL,
json_tok_startswith(buffer, &tok,
"lni1")
type == LIKELY_BOLT12_INV
? &likely_fail : &fail);
if (decodable->invoice) {
decodable->type = "bolt12 invoice";
Expand All @@ -537,8 +623,7 @@ static struct command_result *param_decodable(struct command *cmd,
tok.end - tok.start,
plugin_feature_set(cmd->plugin),
NULL,
json_tok_startswith(buffer, &tok,
"lnr1")
type == LIKELY_BOLT12_INVREQ
? &likely_fail : &fail);
if (decodable->invreq) {
decodable->type = "bolt12 invoice_request";
Expand All @@ -547,22 +632,20 @@ static struct command_result *param_decodable(struct command *cmd,

decodable->emergency_recover = encrypted_decode(cmd, tal_strndup(tmpctx, buffer + tok.start,
tok.end - tok.start),
json_tok_startswith(buffer, &tok,
"clnemerg1")
type == LIKELY_EMERGENCY_RECOVER
? &likely_fail : &fail);

if (decodable->emergency_recover) {
decodable->type = "emergency recover";
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);
type == LIKELY_BOLT11 ? &likely_fail : &fail);
if (decodable->b11) {
decodable->type = "bolt11 invoice";
return NULL;
Expand All @@ -571,10 +654,19 @@ static struct command_result *param_decodable(struct command *cmd,
decodable->rune = rune_from_base64n(decodable, buffer + tok.start,
tok.end - tok.start);
if (decodable->rune) {
decodable->type = "rune";
return NULL;
/* Any bech32 string will "parse" as a rune, but that's not
* helpful. If it isn't all valid UTF8, and it looks like a
* different type, reject it as that one. */
const char *string = rune_to_string(tmpctx, decodable->rune);
if (utf8_check(string, strlen(string)) || type == LIKELY_OTHER) {
decodable->type = "rune";
return NULL;
}
}

if (!likely_fail)
likely_fail = "Unparsable string";

/* Return failure message from most likely parsing candidate */
return command_fail_badparam(cmd, name, buffer, &tok, likely_fail);
}
Expand Down
Loading
Loading