From 8a67c4a1ba8b39b327ff5304e035e347013d8a70 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 26 May 2021 15:16:01 +0930 Subject: [PATCH] decode: always return "valid" field. Otherwise our schema is pretty meaningless, since invalid decodes can have missing "required" fields. Also fix a typo "blinded_payindo". Signed-off-by: Rusty Russell Changelog-Experimental: JSON-RPC: `decode` now gives a `valid` boolean (it does partial decodes of some invalid data). --- plugins/offers.c | 125 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 90 insertions(+), 35 deletions(-) diff --git a/plugins/offers.c b/plugins/offers.c index 5bf5a5da1..fd98b2e13 100644 --- a/plugins/offers.c +++ b/plugins/offers.c @@ -224,7 +224,8 @@ static void json_add_onionmsg_path(struct json_stream *js, json_object_end(js); } -static void json_add_blinded_paths(struct json_stream *js, +/* Returns true if valid */ +static bool json_add_blinded_paths(struct json_stream *js, struct blinded_path **paths, struct blinded_payinfo **blindedpay) { @@ -250,9 +251,13 @@ static void json_add_blinded_paths(struct json_stream *js, * exactly as many `payinfo` as total `onionmsg_path` in * `blinded_path`. */ - if (blindedpay && n != tal_count(blindedpay)) + if (blindedpay && n != tal_count(blindedpay)) { json_add_string(js, "warning_invoice_invalid_blinded_payinfo", "invoice does not have correct number of blinded_payinfo"); + return false; + } + + return true; } static const char *recurrence_time_unit_name(u8 time_unit) @@ -276,6 +281,7 @@ static const char *recurrence_time_unit_name(u8 time_unit) static void json_add_offer(struct json_stream *js, const struct tlv_offer *offer) { struct sha256 offer_id; + bool valid = true; merkle_tlv(offer->fields, &offer_id); json_add_sha256(js, "offer_id", &offer_id); @@ -312,9 +318,11 @@ static void json_add_offer(struct json_stream *js, const struct tlv_offer *offer json_add_stringn(js, "description", offer->description, tal_bytelen(offer->description)); - else + else { json_add_string(js, "warning_offer_missing_description", "offers without a description are invalid"); + valid = false; + } if (offer->vendor) json_add_stringn(js, "vendor", offer->vendor, @@ -325,7 +333,7 @@ static void json_add_offer(struct json_stream *js, const struct tlv_offer *offer json_add_u64(js, "absolute_expiry", *offer->absolute_expiry); if (offer->paths) - json_add_blinded_paths(js, offer->paths, NULL); + valid &= json_add_blinded_paths(js, offer->paths, NULL); if (offer->quantity_min) json_add_u64(js, "quantity_min", *offer->quantity_min); @@ -363,9 +371,12 @@ static void json_add_offer(struct json_stream *js, const struct tlv_offer *offer /* 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); + + json_add_bool(js, "valid", valid); } -static void json_add_fallback_address(struct json_stream *js, +/* Returns true if valid */ +static bool json_add_fallback_address(struct json_stream *js, const struct chainparams *chain, u8 version, const u8 *address) { @@ -373,19 +384,23 @@ static void json_add_fallback_address(struct json_stream *js, /* Does extra checks, in particular checks v0 sizes */ if (segwit_addr_encode(out, chain->bip173_name, version, - address, tal_bytelen(address))) + 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"); + return true; + } + json_add_string(js, + "warning_invoice_fallbacks_address_invalid", + "invalid fallback address for this version"); + return false; } -static void json_add_fallbacks(struct json_stream *js, +/* Returns true if valid */ +static bool json_add_fallbacks(struct json_stream *js, const struct bitcoin_blkid *chains, struct fallback_address **fallbacks) { const struct chainparams *chain; + bool valid = true; /* Present address as first chain mentioned. */ if (tal_count(chains) != 0) @@ -414,23 +429,29 @@ static void json_add_fallbacks(struct json_stream *js, json_add_string(js, "warning_invoice_fallbacks_version_invalid", "invoice fallback version > 16"); + valid = false; } else if (addrlen < 2 || addrlen > 40) { json_add_string(js, "warning_invoice_fallbacks_address_invalid", "invoice fallback address bad length"); + valid = false; } else if (chain) { - json_add_fallback_address(js, chain, - fallbacks[i]->version, - fallbacks[i]->address); + valid &= json_add_fallback_address(js, chain, + fallbacks[i]->version, + fallbacks[i]->address); } json_object_end(js); } json_array_end(js); + + return valid; } static void json_add_b12_invoice(struct json_stream *js, const struct tlv_invoice *invoice) { + bool valid = true; + if (invoice->chains) json_add_chains(js, invoice->chains); if (invoice->offer_id) @@ -442,9 +463,11 @@ static void json_add_b12_invoice(struct json_stream *js, if (invoice->amount) json_add_amount_msat_only(js, "amount_msat", amount_msat(*invoice->amount)); - else + else { json_add_string(js, "warning_invoice_missing_amount", "invoices without an amount are invalid"); + valid = false; + } /* BOLT-offers #12: * - MUST reject the invoice if `description` is not present. @@ -452,9 +475,12 @@ static void json_add_b12_invoice(struct json_stream *js, if (invoice->description) json_add_stringn(js, "description", invoice->description, tal_bytelen(invoice->description)); - else + else { json_add_string(js, "warning_invoice_missing_description", "invoices without a description are invalid"); + valid = false; + } + if (invoice->vendor) json_add_stringn(js, "vendor", invoice->vendor, tal_bytelen(invoice->vendor)); @@ -469,10 +495,12 @@ static void json_add_b12_invoice(struct json_stream *js, * contain exactly as many `payinfo` as total `onionmsg_path` * in `blinded_path`. */ - if (!invoice->blindedpay) + 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); + "invoices with blinded_path without blinded_payinfo are invalid"); + valid = false; + } + valid &= json_add_blinded_paths(js, invoice->paths, invoice->blindedpay); } if (invoice->quantity) json_add_u64(js, "quantity", *invoice->quantity); @@ -494,9 +522,11 @@ static void json_add_b12_invoice(struct json_stream *js, if (invoice->recurrence_basetime) json_add_u64(js, "recurrence_basetime", *invoice->recurrence_basetime); - else + else { json_add_string(js, "warning_invoice_missing_recurrence_basetime", "recurring invoices without a recurrence_basetime are invalid"); + valid = false; + } } if (invoice->payer_key) @@ -509,18 +539,22 @@ static void json_add_b12_invoice(struct json_stream *js, */ if (invoice->timestamp) json_add_u64(js, "timestamp", *invoice->timestamp); - else + else { json_add_string(js, "warning_invoice_missing_timestamp", "invoices without a timestamp are invalid"); + valid = false; + } /* 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 + else { json_add_string(js, "warning_invoice_missing_payment_hash", "invoices without a payment_hash are invalid"); + valid = false; + } /* BOLT-offers #12: * @@ -544,8 +578,8 @@ static void json_add_b12_invoice(struct json_stream *js, json_add_u32(js, "min_final_cltv_expiry", 18); if (invoice->fallbacks) - json_add_fallbacks(js, invoice->chains, - invoice->fallbacks->fallbacks); + valid &= json_add_fallbacks(js, invoice->chains, + invoice->fallbacks->fallbacks); /* BOLT-offers #12: * - if the offer contained `refund_for`: @@ -560,28 +594,37 @@ static void json_add_b12_invoice(struct json_stream *js, if (invoice->refund_signature) { json_add_bip340sig(js, "refund_signature", invoice->refund_signature); - if (!invoice->payer_key) + 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)) + valid = false; + } 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) + valid = false; + } + } else if (invoice->refund_for) { json_add_string(js, "warning_invoice_refund_missing_signature", "refund_for requires refund_signature"); + valid = false; + } /* invoice_decode checked these */ json_add_pubkey32(js, "node_id", invoice->node_id); json_add_bip340sig(js, "signature", invoice->signature); + + json_add_bool(js, "valid", valid); } static void json_add_invoice_request(struct json_stream *js, const struct tlv_invoice_request *invreq) { + bool valid = true; + if (invreq->chains) json_add_chains(js, invreq->chains); /* BOLT-offers #12: @@ -592,9 +635,11 @@ static void json_add_invoice_request(struct json_stream *js, */ if (invreq->offer_id) json_add_sha256(js, "offer_id", invreq->offer_id); - else + else { json_add_string(js, "warning_invoice_request_missing_offer_id", "invoice_request requires offer_id"); + valid = false; + } if (invreq->amount) json_add_amount_msat_only(js, "amount_msat", amount_msat(*invreq->amount)); @@ -611,9 +656,11 @@ static void json_add_invoice_request(struct json_stream *js, *invreq->recurrence_start); if (invreq->payer_key) json_add_pubkey32(js, "payer_key", invreq->payer_key); - else + else { json_add_string(js, "warning_invoice_request_missing_payer_key", "invoice_request requires payer_key"); + valid = false; + } if (invreq->payer_info) json_add_hex_talarr(js, "payer_info", invreq->payer_info); @@ -631,13 +678,18 @@ static void json_add_invoice_request(struct json_stream *js, "invoice_request", "recurrence_signature", invreq->payer_key, - invreq->recurrence_signature)) + invreq->recurrence_signature)) { json_add_string(js, "warning_invoice_request_invalid_recurrence_signature", "Bad recurrence_signature"); + valid = false; + } } else if (invreq->recurrence_counter) { json_add_string(js, "warning_invoice_request_missing_recurrence_signature", "invoice_request requires recurrence_signature"); + valid = false; } + + json_add_bool(js, "valid", valid); } static struct command_result *json_decode(struct command *cmd, @@ -660,8 +712,11 @@ static struct command_result *json_decode(struct command *cmd, json_add_invoice_request(response, decodable->invreq); if (decodable->invoice) json_add_b12_invoice(response, decodable->invoice); - if (decodable->b11) + if (decodable->b11) { + /* The bolt11 decoder simply refuses to decode bad invs. */ json_add_bolt11(response, decodable->b11); + json_add_bool(response, "valid", true); + } return command_finished(cmd, response); }