core-lightning/common/test/run-onion-message-test.c
Rusty Russell 8720bbedae common/onion: split into decode and encode routines.
Some places (e.g. the pay plugin) only need to construct onions,
not decode them.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2022-11-09 15:08:03 +01:00

395 lines
12 KiB
C

/* Creates test vector bolt04/blinded-onion-message-onion-test.json. Run output through jq! */
static void maybe_print(const char *fmt, ...);
#define SUPERVERBOSE maybe_print
#include "config.h"
#include "../../wire/fromwire.c"
#include "../../wire/tlvstream.c"
#include "../../wire/towire.c"
#include "../amount.c"
#include "../bigsize.c"
#include "../blindedpath.c"
#include "../blindedpay.c"
#include "../blinding.c"
#include "../features.c"
#include "../hmac.c"
#include "../onion_encode.c"
#include "../onion_message_parse.c"
#include "../sphinx.c"
#include "../type_to_string.c"
#if EXPERIMENTAL_FEATURES
#include "../../wire/onion_exp_wiregen.c"
#include "../../wire/peer_exp_wiregen.c"
#else
#include "../../wire/onion_wiregen.c"
#include "../../wire/peer_wiregen.c"
#endif
#include <common/ecdh.h>
#include <common/setup.h>
#include <stdio.h>
/* AUTOGENERATED MOCKS START */
/* Generated stub for fromwire_channel_id */
bool fromwire_channel_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED,
struct channel_id *channel_id UNNEEDED)
{ fprintf(stderr, "fromwire_channel_id called!\n"); abort(); }
/* Generated stub for fromwire_node_id */
void fromwire_node_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct node_id *id UNNEEDED)
{ fprintf(stderr, "fromwire_node_id called!\n"); abort(); }
/* Generated stub for new_onionreply */
struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED)
{ fprintf(stderr, "new_onionreply called!\n"); abort(); }
/* Generated stub for pubkey_from_node_id */
bool pubkey_from_node_id(struct pubkey *key UNNEEDED, const struct node_id *id UNNEEDED)
{ fprintf(stderr, "pubkey_from_node_id called!\n"); abort(); }
/* Generated stub for status_fmt */
void status_fmt(enum log_level level UNNEEDED,
const struct node_id *peer UNNEEDED,
const char *fmt UNNEEDED, ...)
{ fprintf(stderr, "status_fmt called!\n"); abort(); }
/* Generated stub for towire_channel_id */
void towire_channel_id(u8 **pptr UNNEEDED, const struct channel_id *channel_id UNNEEDED)
{ fprintf(stderr, "towire_channel_id called!\n"); abort(); }
/* Generated stub for towire_node_id */
void towire_node_id(u8 **pptr UNNEEDED, const struct node_id *id UNNEEDED)
{ fprintf(stderr, "towire_node_id called!\n"); abort(); }
/* AUTOGENERATED MOCKS END */
static bool comma;
static void flush_comma(void)
{
if (comma)
printf(",\n");
comma = false;
}
static void maybe_comma(void)
{
comma = true;
}
static void json_start(const char *name, const char what)
{
flush_comma();
if (name)
printf("\"%s\":", name);
printf("%c\n", what);
}
static void json_end(const char what)
{
comma = false;
printf("%c\n", what);
}
static void json_strfield(const char *name, const char *val)
{
flush_comma();
printf("\t\"%s\": \"%s\"", name, val);
maybe_comma();
}
static void json_hexfield(const char *name, const u8 *val, size_t len)
{
json_strfield(name, tal_hexstr(tmpctx, val, len));
}
static void json_hex_talfield(const char *name, const u8 *val)
{
json_strfield(name, tal_hexstr(tmpctx, val, tal_bytelen(val)));
}
static void json_pubkey(const char *name, const struct pubkey *key)
{
json_strfield(name, pubkey_to_hexstr(tmpctx, key));
}
static bool enable_superverbose;
static void maybe_print(const char *fmt, ...)
{
if (enable_superverbose) {
va_list ap;
va_start(ap, fmt);
vfprintf(stdout, fmt, ap);
va_end(ap);
}
}
/* Updated each time, as we pretend to be Alice, Bob, Carol */
static const struct privkey *mykey;
void ecdh(const struct pubkey *point, struct secret *ss)
{
if (secp256k1_ecdh(secp256k1_ctx, ss->data, &point->pubkey,
mykey->secret.data, NULL, NULL) != 1)
abort();
}
/* This established by trial and error! */
#define LARGEST_TLV_SIZE 70
/* Generic, ugly, function to calc encrypted_recipient_data,
alias and next blinding, and print out JSON */
static u8 *add_hop(const char *name,
const char *comment,
const struct pubkey *me,
const struct pubkey *next,
const struct privkey *override_blinding,
const char *additional_field_hexstr,
const char *path_id_hexstr,
struct privkey *blinding, /* in and out */
struct pubkey *alias /* out */)
{
struct tlv_encrypted_data_tlv *tlv = tlv_encrypted_data_tlv_new(tmpctx);
u8 *enctlv, *encrypted_recipient_data;
const u8 *additional;
json_start(NULL, '{');
json_strfield("alias", name);
json_strfield("comment", comment);
json_hexfield("blinding_secret", blinding->secret.data, sizeof(*blinding));
/* We have to calc first, to make padding correct */
tlv->next_node_id = tal_dup_or_null(tlv, struct pubkey, next);
if (path_id_hexstr)
tlv->path_id = tal_hexdata(tlv, path_id_hexstr,
strlen(path_id_hexstr));
/* Normally we wouldn't know blinding privkey, and we'd just
* paste in the rest of the path as given, but here we're actually
* generating the lot. */
if (override_blinding) {
tlv->next_blinding_override = tal(tlv, struct pubkey);
assert(pubkey_from_privkey(override_blinding, tlv->next_blinding_override));
}
/* This is assumed to be a valid TLV tuple, and greater than
* any previous, so we simply append. */
if (additional_field_hexstr)
additional = tal_hexdata(tlv, additional_field_hexstr,
strlen(additional_field_hexstr));
else
additional = NULL;
enctlv = tal_arr(tmpctx, u8, 0);
towire_tlv_encrypted_data_tlv(&enctlv, tlv);
/* Now create padding, and reencode */
if (tal_bytelen(enctlv) + tal_bytelen(additional) != LARGEST_TLV_SIZE)
tlv->padding = tal_arrz(tlv, u8,
LARGEST_TLV_SIZE
- tal_bytelen(enctlv)
- tal_bytelen(additional)
- 2);
enctlv = tal_arr(tmpctx, u8, 0);
towire_tlv_encrypted_data_tlv(&enctlv, tlv);
towire(&enctlv, additional, tal_bytelen(additional));
assert(tal_bytelen(enctlv) == LARGEST_TLV_SIZE);
json_start("tlvs", '{');
if (tlv->padding)
json_hex_talfield("padding", tlv->padding);
if (tlv->next_node_id)
json_pubkey("next_node_id", tlv->next_node_id);
if (tlv->path_id)
json_hex_talfield("path_id", tlv->path_id);
if (tlv->next_blinding_override) {
json_pubkey("next_blinding_override",
tlv->next_blinding_override);
json_hexfield("blinding_override_secret",
override_blinding->secret.data,
sizeof(*override_blinding));
}
if (additional) {
/* Deconstruct into type, len, value */
size_t totlen = tal_bytelen(additional);
u64 type = fromwire_bigsize(&additional, &totlen);
u64 len = fromwire_bigsize(&additional, &totlen);
assert(len == totlen);
json_hexfield(tal_fmt(tmpctx, "unknown_tag_%"PRIu64, type),
additional, len);
}
json_end('}');
maybe_comma();
json_hex_talfield("encrypted_data_tlv", enctlv);
flush_comma();
enable_superverbose = true;
encrypted_recipient_data = enctlv_from_encmsg_raw(tmpctx,
blinding,
me,
enctlv,
blinding,
alias);
enable_superverbose = false;
json_hex_talfield("encrypted_recipient_data",
encrypted_recipient_data);
if (override_blinding)
*blinding = *override_blinding;
json_end('}');
maybe_comma();
return encrypted_recipient_data;
}
int main(int argc, char *argv[])
{
const char *alias[] = {"Alice", "Bob", "Carol", "Dave"};
struct privkey privkey[ARRAY_SIZE(alias)], blinding, override_blinding;
struct pubkey id[ARRAY_SIZE(alias)], blinding_pub;
struct secret session_key;
u8 *erd[ARRAY_SIZE(alias)]; /* encrypted_recipient_data */
u8 *onion_message_packet, *onion_message;
struct pubkey blinded[ARRAY_SIZE(alias)];
struct sphinx_path *sphinx_path;
struct onionpacket *op;
struct secret *path_secrets;
common_setup(argv[0]);
/* Alice is AAA... Bob is BBB... */
for (size_t i = 0; i < ARRAY_SIZE(alias); i++) {
memset(&privkey[i], alias[i][0], sizeof(privkey[i]));
assert(pubkey_from_privkey(&privkey[i], &id[i]));
}
memset(&session_key, 3, sizeof(session_key));
json_start(NULL, '{');
json_strfield("comment", "Test vector creating an onionmessage, including joining an existing one");
json_start("generate", '{');
json_strfield("comment", "This sections contains test data for Dave's blinded path Bob->Dave; sender has to prepend a hop to Alice to reach Bob");
json_hexfield("session_key", session_key.data, sizeof(session_key));
json_start("hops", '[');
memset(&blinding, 99, sizeof(blinding));
memset(&override_blinding, 1, sizeof(override_blinding));
erd[0] = add_hop("Alice", "Alice->Bob: note next_blinding_override to match that give by Dave for Bob",
&id[0], &id[1],
&override_blinding, NULL, NULL,
&blinding, &blinded[0]);
erd[1] = add_hop("Bob", "Bob->Carol",
&id[1], &id[2],
NULL, "fd023103123456", NULL,
&blinding, &blinded[1]);
erd[2] = add_hop("Carol", "Carol->Dave",
&id[2], &id[3],
NULL, NULL, NULL,
&blinding, &blinded[2]);
erd[3] = add_hop("Dave", "Dave is final node, hence path_id",
&id[3], NULL,
NULL, "fdffff0206c1",
"deadbeefbadc0ffeedeadbeefbadc0ffeedeadbeefbadc0ffeedeadbeefbadc0",
&blinding, &blinded[3]);
json_end(']');
json_end('}');
maybe_comma();
memset(&blinding, 99, sizeof(blinding));
assert(pubkey_from_privkey(&blinding, &blinding_pub));
json_start("route", '{');
json_strfield("comment", "The resulting blinded route Alice to Dave.");
json_pubkey("introduction_node_id", &id[0]);
json_pubkey("blinding", &blinding_pub);
json_start("hops", '[');
for (size_t i = 0; i < ARRAY_SIZE(erd); i++) {
json_start(NULL, '{');
if (i != 0)
json_pubkey("blinded_node_id", &blinded[i]);
json_hex_talfield("encrypted_recipient_data", erd[i]);
json_end('}');
maybe_comma();
}
json_end(']');
json_end('}');
maybe_comma();
json_start("onionmessage", '{');
json_strfield("comment", "An onion message which sends a 'hello' to Dave");
json_strfield("unknown_tag_1", "68656c6c6f");
/* Create the onionmessage */
sphinx_path = sphinx_path_new(tmpctx, NULL);
for (size_t i = 0; i < ARRAY_SIZE(erd); i++) {
struct tlv_onionmsg_tlv *tlv = tlv_onionmsg_tlv_new(tmpctx);
u8 *onionmsg_tlv;
tlv->encrypted_recipient_data = erd[i];
onionmsg_tlv = tal_arr(tmpctx, u8, 0);
/* For final hop, add unknown 'hello' field */
if (i == ARRAY_SIZE(erd) - 1) {
towire_bigsize(&onionmsg_tlv, 1); /* type */
towire_bigsize(&onionmsg_tlv, strlen("hello")); /* length */
towire(&onionmsg_tlv, "hello", strlen("hello"));
}
towire_tlv_onionmsg_tlv(&onionmsg_tlv, tlv);
sphinx_add_hop(sphinx_path, &blinded[i], onionmsg_tlv);
}
/* Make it use our session key! */
sphinx_path->session_key = &session_key;
/* BOLT-onion-message #4:
* - SHOULD set `len` to 1366 or 32834.
*/
op = create_onionpacket(tmpctx, sphinx_path, ROUTING_INFO_SIZE,
&path_secrets);
onion_message_packet = serialize_onionpacket(tmpctx, op);
json_hex_talfield("onion_message_packet", onion_message_packet);
json_end('}');
maybe_comma();
json_start("decrypt", '{');
json_strfield("comment", "This section contains the internal values generated by intermediate nodes when decrypting the onion.");
onion_message = towire_onion_message(tmpctx, &blinding_pub, onion_message_packet);
json_start("hops", '[');
for (size_t i = 0; i < ARRAY_SIZE(erd); i++) {
struct pubkey next_node_id;
struct tlv_onionmsg_tlv *final_om;
struct pubkey final_alias;
struct secret *final_path_id;
json_start(NULL, '{');
json_strfield("alias", alias[i]);
json_hexfield("privkey", privkey[i].secret.data, sizeof(privkey[i]));
json_hex_talfield("onion_message", onion_message);
/* Now, do full decrypt code, to check */
assert(fromwire_onion_message(tmpctx, onion_message,
&blinding_pub, &onion_message_packet));
/* For test_ecdh */
mykey = &privkey[i];
assert(onion_message_parse(tmpctx, onion_message_packet, &blinding_pub, NULL,
&id[i],
&onion_message, &next_node_id,
&final_om,
&final_alias,
&final_path_id));
if (onion_message) {
json_pubkey("next_node_id", &next_node_id);
} else {
const struct tlv_field *hello;
json_start("tlvs", '{');
json_hexfield("path_id", final_path_id->data, sizeof(*final_path_id));
hello = &final_om->fields[0];
json_hexfield(tal_fmt(tmpctx,
"unknown_tag_%"PRIu64,
hello->numtype),
hello->value,
hello->length);
json_end('}');
}
json_end('}');
maybe_comma();
}
json_end(']');
json_end('}');
json_end('}');
common_shutdown();
}