Skip to content

Commit

Permalink
Merge pull request #2412 from valentinewallace/2023-07-construct-blin…
Browse files Browse the repository at this point in the history
…ded-paths

Add API for constructing blinded payment paths
  • Loading branch information
TheBlueMatt authored Aug 23, 2023
2 parents 0c25046 + ea84f2a commit 0211daa
Show file tree
Hide file tree
Showing 13 changed files with 450 additions and 209 deletions.
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>(
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

0 comments on commit 0211daa

Please sign in to comment.