lightningd: new internal JSONRPC "decryptencrypteddata"

I'm not sure about interface yet, so don't document.  It's ugly.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2024-07-17 18:30:11 +09:30
parent e8b959ac77
commit de0d371d20
4 changed files with 102 additions and 5 deletions

View File

@ -156,7 +156,7 @@ bool unblind_onion(const struct pubkey *blinding,
hmac.data) == 1;
}
static u8 *decrypt_encmsg_raw(const tal_t *ctx,
u8 *decrypt_encmsg_raw(const tal_t *ctx,
const struct pubkey *blinding,
const struct secret *ss,
const u8 *enctlv)
@ -253,7 +253,9 @@ void blindedpath_next_blinding(const struct tlv_encrypted_data_tlv *enc,
if (enc->next_blinding_override)
*next_blinding = *enc->next_blinding_override;
else {
/* E_{i-1} = H(E_i || ss_i) * E_i */
/* BOLT #4:
* $`E_{i+1} = SHA256(E_i || ss_i) * E_i`$
*/
struct sha256 h;
blinding_hash_e_and_ss(blinding, ss, &h);
blinding_next_pubkey(blinding, &h, next_blinding);

View File

@ -76,6 +76,12 @@ struct tlv_encrypted_data_tlv *decrypt_encrypted_data(const tal_t *ctx,
const u8 *enctlv)
NON_NULL_ARGS(2, 3);
/* Low-level accessor */
u8 *decrypt_encmsg_raw(const tal_t *ctx,
const struct pubkey *blinding,
const struct secret *ss,
const u8 *enctlv);
/**
* blindedpath_next_blinding - Calculate or extract next blinding pubkey
*/

View File

@ -1,7 +1,9 @@
#include "config.h"
#include <ccan/mem/mem.h>
#include <common/blindedpath.h>
#include <common/blinding.h>
#include <common/configdir.h>
#include <common/ecdh.h>
#include <common/json_command.h>
#include <common/json_param.h>
#include <connectd/connectd_wiregen.h>
@ -328,3 +330,60 @@ static const struct json_command injectonionmessage_command = {
"Unwrap using {blinding}, encoded over {hops} (id, tlv)"
};
AUTODATA(json_command, &injectonionmessage_command);
static struct command_result *json_decryptencrypteddata(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params)
{
u8 *encdata, *decrypted;
struct pubkey *blinding, next_blinding;
struct secret ss;
struct sha256 h;
struct json_stream *response;
if (!param_check(cmd, buffer, params,
p_req("encrypted_data", param_bin_from_hex, &encdata),
p_req("blinding", param_pubkey, &blinding),
NULL))
return command_param_failed();
/* BOLT #4:
*
* - MUST compute:
* - $`ss_i = SHA256(k_i * E_i)`$ (standard ECDH)
*...
* - MUST decrypt the `encrypted_data` field using $`rho_i`$
*/
ecdh(blinding, &ss);
decrypted = decrypt_encmsg_raw(cmd, blinding, &ss, encdata);
if (!decrypted)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Decryption failed!");
if (command_check_only(cmd))
return command_check_done(cmd);
/* BOLT #4:
*
* - $`E_{i+1} = SHA256(E_i || ss_i) * E_i`$
*/
blinding_hash_e_and_ss(blinding, &ss, &h);
blinding_next_pubkey(blinding, &h, &next_blinding);
response = json_stream_success(cmd);
json_object_start(response, "decryptencrypteddata");
json_add_hex_talarr(response, "decrypted", decrypted);
json_add_pubkey(response, "next_blinding", &next_blinding);
json_object_end(response);
return command_success(cmd, response);
}
static const struct json_command decryptencrypteddata_command = {
"decryptencrypteddata",
"utility",
json_decryptencrypteddata,
"Decrypt {encrypted_data} using {blinding}, return decryption and next blinding"
};
AUTODATA(json_command, &decryptencrypteddata_command);

View File

@ -5782,6 +5782,36 @@ def test_onionmessage_ratelimit(node_factory):
l1.rpc.fetchinvoice(offer['bolt12'])
def test_decryptencrypteddata(node_factory):
l1, l2, l3 = node_factory.line_graph(3, fundchannel=False,
opts={'experimental-offers': None})
# Private channel from l2->l3, makes l3 add a blinded path to invoice
# (l1's existence makes sure l3 doesn't see l2 as a dead end!)
node_factory.join_nodes([l1, l2], wait_for_announce=True)
node_factory.join_nodes([l2, l3], announce_channels=False)
wait_for(lambda: ['alias' in n for n in l3.rpc.listnodes()['nodes']] == [True, True])
offer = l3.rpc.offer(amount='2msat', description='test_offer_path_self')
inv = l2.rpc.fetchinvoice(offer['bolt12'])['invoice']
decode = l2.rpc.decode(inv)
path = decode['invoice_paths'][0]
assert path['first_node_id'] == l2.info['id']
blinding = path['blinding']
encdata1 = path['path'][0]['encrypted_recipient_data']
# BOLT #4:
# 1. `tlv_stream`: `encrypted_data_tlv`
# 2. types:
# ...
# 1. type: 4 (`next_node_id`)
# 2. data:
# * [`point`:`node_id`]
dec = l2.rpc.decryptencrypteddata(encrypted_data=encdata1, blinding=blinding)['decryptencrypteddata']
assert dec['decrypted'].startswith('0421' + l3.info['id'])
def test_fetch_no_description_offer(node_factory):
"""Reproducing the issue: https://github.com/ElementsProject/lightning/issues/7405"""
l1, l2 = node_factory.line_graph(2, opts={'experimental-offers': None,