common/blindedpath: generalize routines.

We're going to share them for onion messages as well as for blinded
payments.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2022-10-17 11:13:07 +10:30
parent 85baca56c6
commit 53e40c4380
5 changed files with 181 additions and 151 deletions

View file

@ -19,36 +19,27 @@ static bool blind_node(const struct privkey *blinding,
struct pubkey *node_alias, struct pubkey *node_alias,
struct privkey *next_blinding) struct privkey *next_blinding)
{ {
struct secret node_id_blinding;
struct pubkey blinding_pubkey; struct pubkey blinding_pubkey;
struct sha256 h; struct sha256 h;
/* if (!blindedpath_get_alias(ss, node, node_alias))
* Blinded node_id for N(i), private key known only by N(i):
* B(i) = HMAC256("blinded_node_id", ss(i)) * P(i)
*/
subkey_from_hmac("blinded_node_id", ss, &node_id_blinding);
SUPERVERBOSE("\t\"HMAC256('blinded_node_id', ss)\": \"%s\",\n",
type_to_string(tmpctx, struct secret,
&node_id_blinding));
*node_alias = *node;
if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx,
&node_alias->pubkey,
node_id_blinding.data) != 1)
return false; return false;
SUPERVERBOSE("\t\"blinded_node_id\": \"%s\",\n", SUPERVERBOSE("\t\"blinded_node_id\": \"%s\",\n",
type_to_string(tmpctx, struct pubkey, node_alias)); type_to_string(tmpctx, struct pubkey, node_alias));
/* /* BOLT-route-blinding #4:
* Ephemeral private key, only known by N(r): * - `E(i+1) = SHA256(E(i) || ss(i)) * E(i)`
* e(i+1) = H(E(i) || ss(i)) * e(i) * (NB: `N(i)` MUST NOT learn `e(i)`)
*/ */
if (!pubkey_from_privkey(blinding, &blinding_pubkey)) if (!pubkey_from_privkey(blinding, &blinding_pubkey))
return false; return false;
SUPERVERBOSE("\t\"E\": \"%s\",\n", SUPERVERBOSE("\t\"E\": \"%s\",\n",
type_to_string(tmpctx, struct pubkey, &blinding_pubkey)); type_to_string(tmpctx, struct pubkey, &blinding_pubkey));
/* BOLT-route-blinding #4:
* - `e(i+1) = SHA256(E(i) || ss(i)) * e(i)`
* (blinding ephemeral private key, only known by `N(r)`)
*/
blinding_hash_e_and_ss(&blinding_pubkey, ss, &h); blinding_hash_e_and_ss(&blinding_pubkey, ss, &h);
SUPERVERBOSE("\t\"H(E || ss)\": \"%s\",\n", SUPERVERBOSE("\t\"H(E || ss)\": \"%s\",\n",
type_to_string(tmpctx, struct sha256, &h)); type_to_string(tmpctx, struct sha256, &h));
@ -66,16 +57,15 @@ static u8 *enctlv_from_encmsg_raw(const tal_t *ctx,
struct privkey *next_blinding, struct privkey *next_blinding,
struct pubkey *node_alias) struct pubkey *node_alias)
{ {
/* https://github.com/lightning/bolts/blob/route-blinding/proposals/route-blinding.md */
struct secret ss, rho; struct secret ss, rho;
u8 *ret; u8 *ret;
int ok; int ok;
/* All-zero npub */ /* All-zero npub */
static const unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES]; static const unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES];
/* /* BOLT-route-blinding #4:
* shared secret known only by N(r) and N(i): * - `ss(i) = SHA256(e(i) * N(i)) = SHA256(k(i) * E(i))`
* ss(i) = H(e(i) * P(i)) = H(k(i) * E(i)) * (ECDH shared secret known only by `N(r)` and `N(i)`)
*/ */
if (secp256k1_ecdh(secp256k1_ctx, ss.data, if (secp256k1_ecdh(secp256k1_ctx, ss.data,
&node->pubkey, blinding->secret.data, &node->pubkey, blinding->secret.data,
@ -91,17 +81,20 @@ static u8 *enctlv_from_encmsg_raw(const tal_t *ctx,
ret = tal_dup_talarr(ctx, u8, raw_encmsg); ret = tal_dup_talarr(ctx, u8, raw_encmsg);
SUPERVERBOSE("\t\"encmsg_hex\": \"%s\",\n", tal_hex(tmpctx, ret)); SUPERVERBOSE("\t\"encmsg_hex\": \"%s\",\n", tal_hex(tmpctx, ret));
/* /* BOLT-route-blinding #4:
* Key used to encrypt payload for N(i) by N(r): * - `rho(i) = HMAC256("rho", ss(i))`
* rho(i) = HMAC256("rho", ss(i)) * (key used to encrypt the payload for `N(i)` by `N(r)`)
*/ */
subkey_from_hmac("rho", &ss, &rho); subkey_from_hmac("rho", &ss, &rho);
SUPERVERBOSE("\t\"rho\": \"%s\",\n", SUPERVERBOSE("\t\"rho\": \"%s\",\n",
type_to_string(tmpctx, struct secret, &rho)); type_to_string(tmpctx, struct secret, &rho));
/* BOLT-route-blinding #4:
* - MUST encrypt them with ChaCha20-Poly1305 using the `rho(i)` key
* and an all-zero nonce
*/
/* Encrypt in place */ /* Encrypt in place */
towire_pad(&ret, crypto_aead_chacha20poly1305_ietf_ABYTES); towire_pad(&ret, crypto_aead_chacha20poly1305_ietf_ABYTES);
ok = crypto_aead_chacha20poly1305_ietf_encrypt(ret, NULL, ok = crypto_aead_chacha20poly1305_ietf_encrypt(ret, NULL,
ret, ret,
tal_bytelen(ret) tal_bytelen(ret)
@ -134,15 +127,24 @@ bool unblind_onion(const struct pubkey *blinding,
{ {
struct secret hmac; struct secret hmac;
/* E(i) */ /* BOLT-route-blinding #4:
* An intermediate node in the blinded route:
*
* - MUST compute:
* - `ss(i) = SHA256(k(i) * E(i))` (standard ECDH)
* - `b(i) = HMAC256("blinded_node_id", ss(i)) * k(i)`
*/
ecdh(blinding, ss); ecdh(blinding, ss);
/* b(i) = HMAC256("blinded_node_id", ss(i)) * k(i) */
subkey_from_hmac("blinded_node_id", ss, &hmac); subkey_from_hmac("blinded_node_id", ss, &hmac);
/* We instead tweak the *ephemeral* key from the onion and use /* We instead tweak the *ephemeral* key from the onion and use
* our normal privkey: since hsmd knows only how to ECDH with * our normal privkey: since hsmd knows only how to ECDH with
* our real key */ * our real key. IOW: */
/* BOLT-route-blinding #4:
* - MUST use `b(i)` instead of its private key `k(i)` to decrypt the onion. Note
* that the node may instead tweak the onion ephemeral key with
* `HMAC256("blinded_node_id", ss(i))` which achieves the same result.
*/
return secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, return secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx,
&onion_key->pubkey, &onion_key->pubkey,
hmac.data) == 1; hmac.data) == 1;
@ -158,7 +160,10 @@ static u8 *decrypt_encmsg_raw(const tal_t *ctx,
/* All-zero npub */ /* All-zero npub */
static const unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES]; static const unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES];
/* We need this to decrypt enctlv */ /* BOLT-route-blinding #4:
* - If an `encrypted_data` field is provided:
* - MUST decrypt it using `rho(r)`
*/
subkey_from_hmac("rho", ss, &rho); subkey_from_hmac("rho", ss, &rho);
/* BOLT-onion-message #4: /* BOLT-onion-message #4:
@ -183,7 +188,7 @@ static u8 *decrypt_encmsg_raw(const tal_t *ctx,
return dec; return dec;
} }
static struct tlv_encrypted_data_tlv *decrypt_encmsg(const tal_t *ctx, struct tlv_encrypted_data_tlv *decrypt_encrypted_data(const tal_t *ctx,
const struct pubkey *blinding, const struct pubkey *blinding,
const struct secret *ss, const struct secret *ss,
const u8 *enctlv) const u8 *enctlv)
@ -203,93 +208,48 @@ static struct tlv_encrypted_data_tlv *decrypt_encmsg(const tal_t *ctx,
return fromwire_tlv_encrypted_data_tlv(ctx, &cursor, &maxlen); return fromwire_tlv_encrypted_data_tlv(ctx, &cursor, &maxlen);
} }
bool decrypt_enctlv(const struct pubkey *blinding, bool blindedpath_get_alias(const struct secret *ss,
const struct pubkey *my_id,
struct pubkey *alias)
{
struct secret node_id_blinding;
/* BOLT-route-blinding #4:
* - `B(i) = HMAC256("blinded_node_id", ss(i)) * N(i)`
* (blinded `node_id` for `N(i)`, private key known only by `N(i)`)
*/
subkey_from_hmac("blinded_node_id", ss, &node_id_blinding);
SUPERVERBOSE("\t\"HMAC256('blinded_node_id', ss)\": \"%s\",\n",
type_to_string(tmpctx, struct secret,
&node_id_blinding));
*alias = *my_id;
return secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx,
&alias->pubkey,
node_id_blinding.data) == 1;
}
void blindedpath_next_blinding(const struct tlv_encrypted_data_tlv *enc,
const struct pubkey *blinding,
const struct secret *ss, const struct secret *ss,
const u8 *enctlv,
struct pubkey *next_node,
struct pubkey *next_blinding) struct pubkey *next_blinding)
{ {
struct tlv_encrypted_data_tlv *encmsg; /* BOLT-route
* - `E(1) = SHA256(E(0) || ss(0)) * E(0)`
encmsg = decrypt_encmsg(tmpctx, blinding, ss, enctlv); * ...
if (!encmsg) * - If `encrypted_data` contains a `next_blinding_override`:
return false; * - MUST use it as the next blinding point instead of `E(1)`
* - Otherwise:
/* BOLT-onion-message #4: * - MUST use `E(1)` as the next blinding point
*
* The reader:
* - if it is not the final node according to the onion encryption:
*...
* - if the `enctlv` ... does not contain
* `next_node_id`:
* - MUST drop the message.
*/ */
if (!encmsg->next_node_id) if (enc->next_blinding_override)
return false; *next_blinding = *enc->next_blinding_override;
/* BOLT-onion-message #4:
* The reader:
* - if it is not the final node according to the onion encryption:
*...
* - if the `enctlv` contains `path_id`:
* - MUST drop the message.
*/
if (encmsg->path_id)
return false;
/* BOLT-onion-message #4:
* The reader:
* - if it is not the final node according to the onion encryption:
*...
* - if `blinding` is specified in the `enctlv`:
* - MUST pass that as `blinding` in the `onion_message`
* - otherwise:
* - MUST pass `blinding` derived as in
* [Route Blinding][route-blinding] (i.e.
* `E(i+1) = H(E(i) || ss(i)) * E(i)`).
*/
*next_node = *encmsg->next_node_id;
if (encmsg->next_blinding_override)
*next_blinding = *encmsg->next_blinding_override;
else { else {
/* E(i-1) = H(E(i) || ss(i)) * E(i) */ /* E(i-1) = H(E(i) || ss(i)) * E(i) */
struct sha256 h; struct sha256 h;
blinding_hash_e_and_ss(blinding, ss, &h); blinding_hash_e_and_ss(blinding, ss, &h);
blinding_next_pubkey(blinding, &h, next_blinding); blinding_next_pubkey(blinding, &h, next_blinding);
} }
return true;
}
bool decrypt_final_enctlv(const tal_t *ctx,
const struct pubkey *blinding,
const struct secret *ss,
const u8 *enctlv,
const struct pubkey *my_id,
struct pubkey *alias,
struct secret **path_id)
{
struct tlv_encrypted_data_tlv *encmsg;
struct secret node_id_blinding;
/* Repeat the tweak to get the alias it was using for us */
subkey_from_hmac("blinded_node_id", ss, &node_id_blinding);
*alias = *my_id;
if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx,
&alias->pubkey,
node_id_blinding.data) != 1)
return false;
encmsg = decrypt_encmsg(tmpctx, blinding, ss, enctlv);
if (!encmsg)
return false;
if (tal_bytelen(encmsg->path_id) == sizeof(**path_id)) {
*path_id = tal(ctx, struct secret);
memcpy(*path_id, encmsg->path_id, sizeof(**path_id));
} else
*path_id = NULL;
return true;
} }
u8 *create_enctlv(const tal_t *ctx, u8 *create_enctlv(const tal_t *ctx,

View file

@ -68,41 +68,38 @@ bool unblind_onion(const struct pubkey *blinding,
NO_NULL_ARGS; NO_NULL_ARGS;
/** /**
* decrypt_enctlv - Decrypt an encmsg to form an enctlv. * blindedpath_get_alias - tweak our id to see alias they used.
* @blinding: E(i), the blinding pubkey the previous peer gave us. * @ss: the shared secret from unblind_onion
* @ss: the blinding secret from unblind_onion(). * @my_id: my node_id
* @enctlv: the enctlv from the onion (tal, may be NULL). * @alias: (out) the alias.
* @next_node: (out) the next node_id.
* @next_blinding: (out) the next blinding E(i+1).
* *
* Returns false if decryption failed or encmsg was malformed. * Returns false on ECDH fail.
*/ */
bool decrypt_enctlv(const struct pubkey *blinding, bool blindedpath_get_alias(const struct secret *ss,
const struct secret *ss, const struct pubkey *my_id,
const u8 *enctlv, struct pubkey *alias);
struct pubkey *next_node,
struct pubkey *next_blinding)
NON_NULL_ARGS(1, 2, 4, 5);
/** /**
* decrypt_final_enctlv - Decrypt an encmsg to form an enctlv. * decrypt_encrypted_data - Decrypt an encmsg to form an tlv_encrypted_data_tlv.
* @ctx: tal context for @path_id * @ctx: the context to allocate off.
* @blinding: E(i), the blinding pubkey the previous peer gave us. * @blinding: E(i), the blinding pubkey the previous peer gave us.
* @ss: the blinding secret from unblind_onion(). * @ss: the blinding secret from unblind_onion().
* @enctlv: the enctlv from the onion (tal, may be NULL). * @enctlv: the enctlv from the onion (tal, may be NULL).
* @my_id: the pubkey of this node.
* @alias: (out) the node_id this was addressed to.
* @path_id: (out) the secret contained in the enctlv, if any (NULL if invalid or unset)
* *
* Returns false if decryption failed or encmsg was malformed. * Returns NULL if decryption failed or encmsg was malformed.
*/ */
bool decrypt_final_enctlv(const tal_t *ctx, struct tlv_encrypted_data_tlv *decrypt_encrypted_data(const tal_t *ctx,
const struct pubkey *blinding, const struct pubkey *blinding,
const struct secret *ss, const struct secret *ss,
const u8 *enctlv, const u8 *enctlv)
const struct pubkey *my_id, NON_NULL_ARGS(2, 3);
struct pubkey *alias,
struct secret **path_id) /**
NON_NULL_ARGS(1, 2, 4, 5); * blindedpath_next_blinding - Calculate or extract next blinding pubkey
*/
void blindedpath_next_blinding(const struct tlv_encrypted_data_tlv *enc,
const struct pubkey *blinding,
const struct secret *ss,
struct pubkey *next_blinding);
#endif /* LIGHTNING_COMMON_BLINDEDPATH_H */ #endif /* LIGHTNING_COMMON_BLINDEDPATH_H */

View file

@ -91,19 +91,22 @@ static void test_decrypt(const struct pubkey *blinding,
const struct pubkey *expected_next_node, const struct pubkey *expected_next_node,
const struct privkey *expected_next_blinding_priv) const struct privkey *expected_next_blinding_priv)
{ {
struct pubkey expected_next_blinding, dummy, next_node, next_blinding; struct pubkey expected_next_blinding, dummy, next_blinding;
struct secret ss; struct secret ss;
struct tlv_encrypted_data_tlv *enc;
/* We don't actually have an onion, so we put some dummy */ /* We don't actually have an onion, so we put some dummy */
pubkey_from_privkey(me, &dummy); pubkey_from_privkey(me, &dummy);
mykey = me; mykey = me;
assert(unblind_onion(blinding, test_ecdh, &dummy, &ss)); assert(unblind_onion(blinding, test_ecdh, &dummy, &ss));
assert(decrypt_enctlv(blinding, &ss, enctlv, &next_node, &next_blinding)); enc = decrypt_encrypted_data(tmpctx, blinding, &ss, enctlv);
assert(enc);
pubkey_from_privkey(expected_next_blinding_priv, &expected_next_blinding); pubkey_from_privkey(expected_next_blinding_priv, &expected_next_blinding);
blindedpath_next_blinding(enc, blinding, &ss, &next_blinding);
assert(pubkey_eq(&next_blinding, &expected_next_blinding)); assert(pubkey_eq(&next_blinding, &expected_next_blinding));
assert(pubkey_eq(&next_node, expected_next_node)); assert(pubkey_eq(enc->next_node_id, expected_next_node));
} }
static void test_final_decrypt(const struct pubkey *blinding, static void test_final_decrypt(const struct pubkey *blinding,
@ -113,7 +116,8 @@ static void test_final_decrypt(const struct pubkey *blinding,
const struct secret *expected_self_id) const struct secret *expected_self_id)
{ {
struct pubkey my_pubkey, dummy, alias; struct pubkey my_pubkey, dummy, alias;
struct secret ss, *self_id; struct secret ss;
struct tlv_encrypted_data_tlv *enc;
/* We don't actually have an onion, so we put some dummy */ /* We don't actually have an onion, so we put some dummy */
pubkey_from_privkey(me, &dummy); pubkey_from_privkey(me, &dummy);
@ -121,11 +125,13 @@ static void test_final_decrypt(const struct pubkey *blinding,
mykey = me; mykey = me;
pubkey_from_privkey(me, &my_pubkey); pubkey_from_privkey(me, &my_pubkey);
assert(unblind_onion(blinding, test_ecdh, &dummy, &ss)); assert(unblind_onion(blinding, test_ecdh, &dummy, &ss));
assert(decrypt_final_enctlv(tmpctx, blinding, &ss, enctlv, &my_pubkey, enc = decrypt_encrypted_data(tmpctx, blinding, &ss, enctlv);
&alias, &self_id)); assert(enc);
assert(blindedpath_get_alias(&ss, &my_pubkey, &alias));
assert(pubkey_eq(&alias, expected_alias)); assert(pubkey_eq(&alias, expected_alias));
assert(secret_eq_consttime(self_id, expected_self_id)); assert(memeq(enc->path_id, tal_bytelen(enc->path_id), expected_self_id,
sizeof(*expected_self_id)));
} }
int main(int argc, char *argv[]) int main(int argc, char *argv[])

View file

@ -108,12 +108,13 @@ static u8 *next_onion(const tal_t *ctx, u8 *omsg,
{ {
struct onionpacket *op; struct onionpacket *op;
struct pubkey blinding, ephemeral; struct pubkey blinding, ephemeral;
struct pubkey next_node, next_blinding; struct pubkey next_blinding;
struct tlv_onionmsg_payload *om; struct tlv_onionmsg_payload *om;
struct secret ss, onion_ss; struct secret ss, onion_ss;
const u8 *cursor; const u8 *cursor;
size_t max, maxlen; size_t max, maxlen;
struct route_step *rs; struct route_step *rs;
struct tlv_encrypted_data_tlv *enc;
assert(fromwire_onion_message(tmpctx, omsg, &blinding, &omsg)); assert(fromwire_onion_message(tmpctx, omsg, &blinding, &omsg));
assert(pubkey_eq(&blinding, expected_blinding)); assert(pubkey_eq(&blinding, expected_blinding));
@ -137,8 +138,9 @@ static u8 *next_onion(const tal_t *ctx, u8 *omsg,
if (rs->nextcase == ONION_END) if (rs->nextcase == ONION_END)
return NULL; return NULL;
assert(decrypt_enctlv(&blinding, &ss, om->encrypted_data_tlv, &next_node, enc = decrypt_encrypted_data(tmpctx, &blinding, &ss, om->encrypted_data_tlv);
&next_blinding)); assert(enc);
blindedpath_next_blinding(enc, &blinding, &ss, &next_blinding);
return towire_onion_message(ctx, &next_blinding, return towire_onion_message(ctx, &next_blinding,
serialize_onionpacket(tmpctx, rs->next)); serialize_onionpacket(tmpctx, rs->next));
} }

View file

@ -35,6 +35,71 @@ void onionmsg_req(struct daemon *daemon, const u8 *msg)
} }
} }
static bool decrypt_final_onionmsg(const tal_t *ctx,
const struct pubkey *blinding,
const struct secret *ss,
const u8 *enctlv,
const struct pubkey *my_id,
struct pubkey *alias,
struct secret **path_id)
{
struct tlv_encrypted_data_tlv *encmsg;
if (!blindedpath_get_alias(ss, my_id, alias))
return false;
encmsg = decrypt_encrypted_data(tmpctx, blinding, ss, enctlv);
if (!encmsg)
return false;
if (tal_bytelen(encmsg->path_id) == sizeof(**path_id)) {
*path_id = tal(ctx, struct secret);
memcpy(*path_id, encmsg->path_id, sizeof(**path_id));
} else
*path_id = NULL;
return true;
}
static bool decrypt_forwarding_onionmsg(const struct pubkey *blinding,
const struct secret *ss,
const u8 *enctlv,
struct pubkey *next_node,
struct pubkey *next_blinding)
{
struct tlv_encrypted_data_tlv *encmsg;
encmsg = decrypt_encrypted_data(tmpctx, blinding, ss, enctlv);
if (!encmsg)
return false;
/* BOLT-onion-message #4:
*
* The reader:
* - if it is not the final node according to the onion encryption:
*...
* - if the `enctlv` ... does not contain
* `next_node_id`:
* - MUST drop the message.
*/
if (!encmsg->next_node_id)
return false;
/* BOLT-onion-message #4:
* The reader:
* - if it is not the final node according to the onion encryption:
*...
* - if the `enctlv` contains `path_id`:
* - MUST drop the message.
*/
if (encmsg->path_id)
return false;
*next_node = *encmsg->next_node_id;
blindedpath_next_blinding(encmsg, blinding, ss, next_blinding);
return true;
}
/* Peer sends an onion msg. */ /* Peer sends an onion msg. */
void handle_onion_message(struct daemon *daemon, void handle_onion_message(struct daemon *daemon,
struct peer *peer, const u8 *msg) struct peer *peer, const u8 *msg)
@ -123,7 +188,7 @@ void handle_onion_message(struct daemon *daemon,
if (!om->encrypted_data_tlv) { if (!om->encrypted_data_tlv) {
alias = me; alias = me;
self_id = NULL; self_id = NULL;
} else if (!decrypt_final_enctlv(tmpctx, &blinding, &ss, } else if (!decrypt_final_onionmsg(tmpctx, &blinding, &ss,
om->encrypted_data_tlv, &me, &alias, om->encrypted_data_tlv, &me, &alias,
&self_id)) { &self_id)) {
status_peer_debug(&peer->id, status_peer_debug(&peer->id,
@ -159,7 +224,7 @@ void handle_onion_message(struct daemon *daemon,
struct node_id next_node_id; struct node_id next_node_id;
/* This fails as expected if no enctlv. */ /* This fails as expected if no enctlv. */
if (!decrypt_enctlv(&blinding, &ss, om->encrypted_data_tlv, &next_node, if (!decrypt_forwarding_onionmsg(&blinding, &ss, om->encrypted_data_tlv, &next_node,
&next_blinding)) { &next_blinding)) {
status_peer_debug(&peer->id, status_peer_debug(&peer->id,
"onion msg: invalid enctlv %s", "onion msg: invalid enctlv %s",