Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API for constructing blinded payment paths #2412

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions lightning/src/blinded_path/message.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};

use crate::blinded_path::{BlindedHop, BlindedPath};
use crate::blinded_path::utils;
use crate::io;
use crate::io::Cursor;
use crate::ln::onion_utils;
use crate::onion_message::ControlTlvs;
use crate::prelude::*;
use crate::sign::{NodeSigner, Recipient};
use crate::util::chacha20poly1305rfc::ChaChaPolyReadAdapter;
use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Writeable, Writer};

use core::mem;
use core::ops::Deref;

/// TLVs to encode in an intermediate onion message packet's hop data. When provided in a blinded
/// route, they are encoded into [`BlindedHop::encrypted_payload`].
pub(crate) struct ForwardTlvs {
/// The node id of the next hop in the onion message's path.
pub(crate) next_node_id: PublicKey,
/// Senders to a blinded path use this value to concatenate the route they find to the
/// introduction node with the blinded path.
pub(crate) next_blinding_override: Option<PublicKey>,
}

/// Similar to [`ForwardTlvs`], but these TLVs are for the final node.
pub(crate) struct ReceiveTlvs {
/// If `path_id` is `Some`, it is used to identify the blinded path that this onion message is
/// sending to. This is useful for receivers to check that said blinded path is being used in
/// the right context.
pub(crate) path_id: Option<[u8; 32]>,
}

impl Writeable for ForwardTlvs {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
// TODO: write padding
encode_tlv_stream!(writer, {
(4, self.next_node_id, required),
(8, self.next_blinding_override, option)
});
Ok(())
}
}

impl Writeable for ReceiveTlvs {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
// TODO: write padding
encode_tlv_stream!(writer, {
(6, self.path_id, option),
});
Ok(())
}
}

/// Construct blinded onion message hops for the given `unblinded_path`.
pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], session_priv: &SecretKey
) -> Result<Vec<BlindedHop>, secp256k1::Error> {
let blinded_tlvs = unblinded_path.iter()
.skip(1) // The first node's TLVs contains the next node's pubkey
.map(|pk| {
ControlTlvs::Forward(ForwardTlvs { next_node_id: *pk, next_blinding_override: None })
})
.chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs { path_id: None })));

utils::construct_blinded_hops(secp_ctx, unblinded_path.iter(), blinded_tlvs, session_priv)
}

// Advance the blinded onion message path by one hop, so make the second hop into the new
// introduction node.
pub(crate) fn advance_path_by_one<NS: Deref, T: secp256k1::Signing + secp256k1::Verification>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need the same utility for blinded payment paths (and do they have the same control tlvs)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was planning to do this as a follow-up to #2413, is your preference to get it out sooner? The control TLVs aren't the same.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that's alright, I just wanted to make sure that the move made sense - I guess the follow-up will mean basically copying this and writing some new code to do the same thing for blinded payment paths?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that's alright, I just wanted to make sure that the move made sense - I guess the follow-up will mean basically copying this and writing some new code to do the same thing for blinded payment paths?

Yep, that's the thinking

path: &mut BlindedPath, node_signer: &NS, secp_ctx: &Secp256k1<T>
) -> Result<(), ()> where NS::Target: NodeSigner {
let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &path.blinding_point, None)?;
let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes());
let encrypted_control_tlvs = path.blinded_hops.remove(0).encrypted_payload;
let mut s = Cursor::new(&encrypted_control_tlvs);
let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64);
match ChaChaPolyReadAdapter::read(&mut reader, rho) {
Ok(ChaChaPolyReadAdapter { readable: ControlTlvs::Forward(ForwardTlvs {
mut next_node_id, next_blinding_override,
})}) => {
let mut new_blinding_point = match next_blinding_override {
Some(blinding_point) => blinding_point,
None => {
onion_utils::next_hop_pubkey(secp_ctx, path.blinding_point,
control_tlvs_ss.as_ref()).map_err(|_| ())?
}
};
mem::swap(&mut path.blinding_point, &mut new_blinding_point);
mem::swap(&mut path.introduction_node_id, &mut next_node_id);
Ok(())
},
_ => Err(())
}
}
151 changes: 30 additions & 121 deletions lightning/src/blinded_path/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,17 @@

//! Creating blinded paths and related utilities live here.

pub mod payment;
pub(crate) mod message;
pub(crate) mod utils;

use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};

use crate::sign::{EntropySource, NodeSigner, Recipient};
use crate::onion_message::ControlTlvs;
use crate::sign::EntropySource;
use crate::ln::msgs::DecodeError;
use crate::ln::onion_utils;
use crate::util::chacha20poly1305rfc::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter};
use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Readable, VecWriter, Writeable, Writer};
use crate::util::ser::{Readable, Writeable, Writer};

use core::mem;
use core::ops::Deref;
use crate::io::{self, Cursor};
use crate::io;
use crate::prelude::*;

/// Onion messages and payments can be sent and received to blinded paths, which serve to hide the
Expand All @@ -44,13 +41,14 @@ pub struct BlindedPath {
pub blinded_hops: Vec<BlindedHop>,
}

/// Used to construct the blinded hops portion of a blinded path. These hops cannot be identified
/// by outside observers and thus can be used to hide the identity of the recipient.
/// An encrypted payload and node id corresponding to a hop in a payment or onion message path, to
/// be encoded in the sender's onion packet. These hops cannot be identified by outside observers
/// and thus can be used to hide the identity of the recipient.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct BlindedHop {
/// The blinded node id of this hop in a blinded path.
/// The blinded node id of this hop in a [`BlindedPath`].
pub blinded_node_id: PublicKey,
/// The encrypted payload intended for this hop in a blinded path.
/// The encrypted payload intended for this hop in a [`BlindedPath`].
// The node sending to this blinded path will later encode this payload into the onion packet for
// this hop.
pub encrypted_payload: Vec<u8>,
Expand All @@ -73,81 +71,30 @@ impl BlindedPath {
Ok(BlindedPath {
introduction_node_id,
blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
blinded_hops: blinded_message_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?,
blinded_hops: message::blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?,
})
}

// Advance the blinded onion message path by one hop, so make the second hop into the new
// introduction node.
pub(super) fn advance_message_path_by_one<NS: Deref, T: secp256k1::Signing + secp256k1::Verification>
(&mut self, node_signer: &NS, secp_ctx: &Secp256k1<T>) -> Result<(), ()>
where NS::Target: NodeSigner
{
let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &self.blinding_point, None)?;
let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes());
let encrypted_control_tlvs = self.blinded_hops.remove(0).encrypted_payload;
let mut s = Cursor::new(&encrypted_control_tlvs);
let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64);
match ChaChaPolyReadAdapter::read(&mut reader, rho) {
Ok(ChaChaPolyReadAdapter { readable: ControlTlvs::Forward(ForwardTlvs {
mut next_node_id, next_blinding_override,
})}) => {
let mut new_blinding_point = match next_blinding_override {
Some(blinding_point) => blinding_point,
None => {
onion_utils::next_hop_pubkey(secp_ctx, self.blinding_point,
control_tlvs_ss.as_ref()).map_err(|_| ())?
}
};
mem::swap(&mut self.blinding_point, &mut new_blinding_point);
mem::swap(&mut self.introduction_node_id, &mut next_node_id);
Ok(())
},
_ => Err(())
}
}
}

/// Construct blinded onion message hops for the given `unblinded_path`.
fn blinded_message_hops<T: secp256k1::Signing + secp256k1::Verification>(
secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], session_priv: &SecretKey
) -> Result<Vec<BlindedHop>, secp256k1::Error> {
let mut blinded_hops = Vec::with_capacity(unblinded_path.len());

let mut prev_ss_and_blinded_node_id = None;
utils::construct_keys_callback(secp_ctx, unblinded_path, None, session_priv, |blinded_node_id, _, _, encrypted_payload_ss, unblinded_pk, _| {
if let Some((prev_ss, prev_blinded_node_id)) = prev_ss_and_blinded_node_id {
if let Some(pk) = unblinded_pk {
let payload = ForwardTlvs {
next_node_id: pk,
next_blinding_override: None,
};
blinded_hops.push(BlindedHop {
blinded_node_id: prev_blinded_node_id,
encrypted_payload: encrypt_payload(payload, prev_ss),
});
} else { debug_assert!(false); }
}
prev_ss_and_blinded_node_id = Some((encrypted_payload_ss, blinded_node_id));
})?;

if let Some((final_ss, final_blinded_node_id)) = prev_ss_and_blinded_node_id {
let final_payload = ReceiveTlvs { path_id: None };
blinded_hops.push(BlindedHop {
blinded_node_id: final_blinded_node_id,
encrypted_payload: encrypt_payload(final_payload, final_ss),
});
} else { debug_assert!(false) }

Ok(blinded_hops)
}
/// Create a blinded path for a payment, to be forwarded along `path`. The last node
/// in `path` will be the destination node.
///
/// Errors if `path` is empty or a node id in `path` is invalid.
// TODO: make all payloads the same size with padding + add dummy hops
pub fn new_for_payment<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>(
intermediate_nodes: &[(PublicKey, payment::ForwardTlvs)], payee_node_id: PublicKey,
payee_tlvs: payment::ReceiveTlvs, entropy_source: &ES, secp_ctx: &Secp256k1<T>
) -> Result<Self, ()> {
let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");

/// Encrypt TLV payload to be used as a [`BlindedHop::encrypted_payload`].
fn encrypt_payload<P: Writeable>(payload: P, encrypted_tlvs_ss: [u8; 32]) -> Vec<u8> {
let mut writer = VecWriter(Vec::new());
let write_adapter = ChaChaPolyWriteAdapter::new(encrypted_tlvs_ss, &payload);
write_adapter.write(&mut writer).expect("In-memory writes cannot fail");
writer.0
Ok(BlindedPath {
introduction_node_id: intermediate_nodes.first().map_or(payee_node_id, |n| n.0),
blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
blinded_hops: payment::blinded_hops(
secp_ctx, intermediate_nodes, payee_node_id, payee_tlvs, &blinding_secret
).map_err(|_| ())?,
})
}
}

impl Writeable for BlindedPath {
Expand Down Expand Up @@ -185,41 +132,3 @@ impl_writeable!(BlindedHop, {
encrypted_payload
});

/// TLVs to encode in an intermediate onion message packet's hop data. When provided in a blinded
/// route, they are encoded into [`BlindedHop::encrypted_payload`].
pub(crate) struct ForwardTlvs {
/// The node id of the next hop in the onion message's path.
pub(super) next_node_id: PublicKey,
/// Senders to a blinded path use this value to concatenate the route they find to the
/// introduction node with the blinded path.
pub(super) next_blinding_override: Option<PublicKey>,
}

/// Similar to [`ForwardTlvs`], but these TLVs are for the final node.
pub(crate) struct ReceiveTlvs {
/// If `path_id` is `Some`, it is used to identify the blinded path that this onion message is
/// sending to. This is useful for receivers to check that said blinded path is being used in
/// the right context.
pub(super) path_id: Option<[u8; 32]>,
}

impl Writeable for ForwardTlvs {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
// TODO: write padding
encode_tlv_stream!(writer, {
(4, self.next_node_id, required),
(8, self.next_blinding_override, option)
});
Ok(())
}
}

impl Writeable for ReceiveTlvs {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
// TODO: write padding
encode_tlv_stream!(writer, {
(6, self.path_id, option),
});
Ok(())
}
}
Loading