diff --git a/channeld/channeld.c b/channeld/channeld.c index cbb11862d..a6643e9c2 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -55,6 +55,7 @@ #include #include #include +#include #include #include #include @@ -1623,12 +1624,44 @@ static bool channeld_handle_custommsg(const u8 *msg) } #if EXPERIMENTAL_FEATURES +/* H(E(i) || ss(i)) */ +static struct sha256 hash_e_and_ss(const struct pubkey *e, + const struct secret *ss) +{ + u8 der[PUBKEY_CMPR_LEN]; + struct sha256_ctx shactx; + struct sha256 h; + + pubkey_to_der(der, e); + sha256_init(&shactx); + sha256_update(&shactx, der, sizeof(der)); + sha256_update(&shactx, ss->data, sizeof(ss->data)); + sha256_done(&shactx, &h); + + return h; +} + +/* E(i-1) = H(E(i) || ss(i)) * E(i) */ +static struct pubkey next_pubkey(const struct pubkey *pk, + const struct sha256 *h) +{ + struct pubkey ret; + + ret = *pk; + if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, &ret.pubkey, h->u.u8) + != 1) + abort(); + + return ret; +} + /* Peer sends onion msg. */ static void handle_onion_message(struct peer *peer, const u8 *msg) { enum onion_type badreason; struct onionpacket op; - struct secret ss; + struct secret ss, *blinding_ss; + struct pubkey *blinding_in; struct route_step *rs; u8 onion[TOTAL_PACKET_SIZE]; const u8 *cursor; @@ -1643,11 +1676,6 @@ static void handle_onion_message(struct peer *peer, const u8 *msg) &peer->channel_id, "Bad onion_message %s", tal_hex(peer, msg)); - if (tlvs->blinding) { - status_broken("FIXME: Handle blinding!"); - return; - } - /* We unwrap the onion now. */ badreason = parse_onionpacket(onion, TOTAL_PACKET_SIZE, &op); if (badreason != 0) { @@ -1656,7 +1684,38 @@ static void handle_onion_message(struct peer *peer, const u8 *msg) return; } - /* Because wire takes struct pubkey. */ + if (tlvs->blinding) { + struct secret hmac; + + /* E(i) */ + blinding_in = tal(msg, struct pubkey); + *blinding_in = tlvs->blinding->blinding; + status_debug("blinding in = %s", + type_to_string(tmpctx, struct pubkey, blinding_in)); + blinding_ss = tal(msg, struct secret); + msg = hsm_req(tmpctx, towire_hsm_ecdh_req(tmpctx, blinding_in)); + + if (!fromwire_hsm_ecdh_resp(msg, blinding_ss)) + status_failed(STATUS_FAIL_HSM_IO, + "Reading ecdh response for blinding"); + + /* b(i) = HMAC256("blinded_node_id", ss(i)) * k(i) */ + subkey_from_hmac("blinded_node_id", blinding_ss, &hmac); + + /* We instead tweak the *ephemeral* key from the onion and use + * our normal privkey: since hsmd knows only how to ECDH with + * our real key */ + if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, + &op.ephemeralkey.pubkey, + hmac.data) != 1) { + status_debug("onion msg: can't tweak pubkey"); + return; + } + } else { + blinding_ss = NULL; + blinding_in = NULL; + } + msg = hsm_req(tmpctx, towire_hsm_ecdh_req(tmpctx, &op.ephemeralkey)); if (!fromwire_hsm_ecdh_resp(msg, &ss)) status_failed(STATUS_FAIL_HSM_IO, "Reading ecdh response"); @@ -1692,6 +1751,72 @@ static void handle_onion_message(struct peer *peer, const u8 *msg) return; } + /* If we weren't given a blinding factor, tlv can provide one. */ + if (om->blinding && !blinding_ss) { + /* E(i) */ + blinding_in = tal(msg, struct pubkey); + *blinding_in = om->blinding->blinding; + blinding_ss = tal(msg, struct secret); + + msg = hsm_req(tmpctx, towire_hsm_ecdh_req(tmpctx, blinding_in)); + + if (!fromwire_hsm_ecdh_resp(msg, blinding_ss)) + status_failed(STATUS_FAIL_HSM_IO, + "Reading ecdh response for om blinding"); + } + + if (om->enctlv) { + const unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + u8 *dec; + struct secret rho; + int ret; + + if (!blinding_ss) { + status_debug("enctlv but no blinding?"); + return; + } + + /* We need this to decrypt enctlv */ + subkey_from_hmac("rho", blinding_ss, &rho); + + /* Overrides next_scid / next_node */ + if (tal_bytelen(om->enctlv->enctlv) + < crypto_aead_chacha20poly1305_ietf_ABYTES) { + status_debug("enctlv too short for mac"); + return; + } + + dec = tal_arr(msg, u8, + tal_bytelen(om->enctlv->enctlv) + - crypto_aead_chacha20poly1305_ietf_ABYTES); + ret = crypto_aead_chacha20poly1305_ietf_decrypt(dec, NULL, + NULL, + om->enctlv->enctlv, + tal_bytelen(om->enctlv->enctlv), + NULL, 0, + npub, + rho.data); + if (ret != 0) + errx(1, "Failed to decrypt enctlv field"); + + status_debug("enctlv -> %s", tal_hex(tmpctx, dec)); + + /* Replace onionmsg with one from enctlv */ + cursor = dec; + maxlen = tal_bytelen(dec); + + om = tlv_onionmsg_payload_new(msg); + if (!fromwire_onionmsg_payload(&cursor, &maxlen, om)) { + status_debug("onion msg: invalid enctlv onionmsg_payload %s", + tal_hex(tmpctx, dec)); + return; + } + } else if (blinding_ss && rs->nextcase != ONION_END) { + status_debug("Onion had %s, but not enctlv?", + tlvs->blinding ? "blinding" : "om blinding"); + return; + } + if (om->next_short_channel_id) next_scid = &om->next_short_channel_id->short_channel_id; else @@ -1709,13 +1834,24 @@ static void handle_onion_message(struct peer *peer, const u8 *msg) } if (rs->nextcase == ONION_END) { - /* FIXME: Handle reply_path */ - /* payload may not be valid, so we hand it pre-decrypted to lightningd */ + struct pubkey *blinding; + const struct onionmsg_path **path; + + if (om->reply_path) { + blinding = &om->reply_path->blinding; + path = cast_const2(const struct onionmsg_path **, + om->reply_path->path); + } else { + blinding = NULL; + path = NULL; + } wire_sync_write(MASTER_FD, take(towire_got_onionmsg_to_us(NULL, - NULL, - NULL))); + blinding, + path))); } else { + struct pubkey *next_blinding; + /* This *MUST* have instructions on where to go next. */ if (!next_scid && !next_node) { status_debug("onion msg: no next field in %s", @@ -1723,11 +1859,19 @@ static void handle_onion_message(struct peer *peer, const u8 *msg) return; } + if (blinding_ss) { + /* E(i-1) = H(E(i) || ss(i)) * E(i) */ + struct sha256 h = hash_e_and_ss(blinding_in, blinding_ss); + next_blinding = tal(msg, struct pubkey); + *next_blinding = next_pubkey(blinding_in, &h); + } else + next_blinding = NULL; + wire_sync_write(MASTER_FD, take(towire_got_onionmsg_forward(NULL, next_scid, next_node, - NULL, + next_blinding, serialize_onionpacket(tmpctx, rs->next)))); } } diff --git a/tests/test_misc.py b/tests/test_misc.py index aecd96ae4..2e08cee12 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -2182,6 +2182,8 @@ def test_sendcustommsg(node_factory): def test_sendonionmessage(node_factory): l1, l2, l3 = node_factory.line_graph(3) + blindedpathtool = os.path.join(os.path.dirname(__file__), "..", "devtools", "blindedpath") + l1.rpc.call('sendonionmessage', {'hops': [{'id': l2.info['id']}, @@ -2196,7 +2198,23 @@ def test_sendonionmessage(node_factory): {'id': l3.info['id']}]}) assert l3.daemon.wait_for_log('Got onionmsg') - # FIXME: Test blinded path! + # Now test blinded path. + output = subprocess.check_output( + [blindedpathtool, '--simple-output', 'create', l2.info['id'], l3.info['id']] + ).decode('ASCII').strip() + + # First line is blinding, then then . + blinding, p1, p1enc, p2 = output.split('\n') + # First hop can't be blinded! + assert p1 == l2.info['id'] + + l1.rpc.call('sendonionmessage', + {'hops': + [{'id': l2.info['id'], + 'blinding': blinding, + 'enctlv': p1enc}, + {'id': p2}]}) + assert l3.daemon.wait_for_log('Got onionmsg') @unittest.skipIf(not DEVELOPER, "needs --dev-force-privkey")