mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-02-24 15:02:20 +01:00
Merge pull request #1503 from valentinewallace/2022-05-onion-msgs
Onion messages v1
This commit is contained in:
commit
28c9b56113
17 changed files with 1325 additions and 50 deletions
|
@ -54,6 +54,7 @@ use utils::test_logger::{self, Output};
|
|||
use utils::test_persister::TestPersister;
|
||||
|
||||
use bitcoin::secp256k1::{PublicKey,SecretKey};
|
||||
use bitcoin::secp256k1::ecdh::SharedSecret;
|
||||
use bitcoin::secp256k1::ecdsa::RecoverableSignature;
|
||||
use bitcoin::secp256k1::Secp256k1;
|
||||
|
||||
|
@ -165,6 +166,14 @@ impl KeysInterface for KeyProvider {
|
|||
Ok(SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, self.node_id]).unwrap())
|
||||
}
|
||||
|
||||
fn ecdh(&self, recipient: Recipient, other_key: &PublicKey, tweak: Option<&[u8; 32]>) -> Result<SharedSecret, ()> {
|
||||
let mut node_secret = self.get_node_secret(recipient)?;
|
||||
if let Some(tweak) = tweak {
|
||||
node_secret.mul_assign(tweak).map_err(|_| ())?;
|
||||
}
|
||||
Ok(SharedSecret::new(other_key, &node_secret))
|
||||
}
|
||||
|
||||
fn get_inbound_payment_key_material(&self) -> KeyMaterial {
|
||||
KeyMaterial([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, self.node_id])
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ use utils::test_logger;
|
|||
use utils::test_persister::TestPersister;
|
||||
|
||||
use bitcoin::secp256k1::{PublicKey,SecretKey};
|
||||
use bitcoin::secp256k1::ecdh::SharedSecret;
|
||||
use bitcoin::secp256k1::ecdsa::RecoverableSignature;
|
||||
use bitcoin::secp256k1::Secp256k1;
|
||||
|
||||
|
@ -269,6 +270,14 @@ impl KeysInterface for KeyProvider {
|
|||
Ok(self.node_secret.clone())
|
||||
}
|
||||
|
||||
fn ecdh(&self, recipient: Recipient, other_key: &PublicKey, tweak: Option<&[u8; 32]>) -> Result<SharedSecret, ()> {
|
||||
let mut node_secret = self.get_node_secret(recipient)?;
|
||||
if let Some(tweak) = tweak {
|
||||
node_secret.mul_assign(tweak).map_err(|_| ())?;
|
||||
}
|
||||
Ok(SharedSecret::new(other_key, &node_secret))
|
||||
}
|
||||
|
||||
fn get_inbound_payment_key_material(&self) -> KeyMaterial {
|
||||
self.inbound_payment_key.clone()
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ use bitcoin::hash_types::WPubkeyHash;
|
|||
|
||||
use bitcoin::secp256k1::{SecretKey, PublicKey};
|
||||
use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature, Signing};
|
||||
use bitcoin::secp256k1::ecdh::SharedSecret;
|
||||
use bitcoin::secp256k1::ecdsa::RecoverableSignature;
|
||||
use bitcoin::{secp256k1, Witness};
|
||||
|
||||
|
@ -404,6 +405,12 @@ pub trait KeysInterface {
|
|||
/// This method must return the same value each time it is called with a given `Recipient`
|
||||
/// parameter.
|
||||
fn get_node_secret(&self, recipient: Recipient) -> Result<SecretKey, ()>;
|
||||
/// Gets the ECDH shared secret of our [`node secret`] and `other_key`, multiplying by `tweak` if
|
||||
/// one is provided. Note that this tweak can be applied to `other_key` instead of our node
|
||||
/// secret, though this is less efficient.
|
||||
///
|
||||
/// [`node secret`]: Self::get_node_secret
|
||||
fn ecdh(&self, recipient: Recipient, other_key: &PublicKey, tweak: Option<&[u8; 32]>) -> Result<SharedSecret, ()>;
|
||||
/// Get a script pubkey which we send funds to when claiming on-chain contestable outputs.
|
||||
///
|
||||
/// This method should return a different value each time it is called, to avoid linking
|
||||
|
@ -1133,6 +1140,14 @@ impl KeysInterface for KeysManager {
|
|||
}
|
||||
}
|
||||
|
||||
fn ecdh(&self, recipient: Recipient, other_key: &PublicKey, tweak: Option<&[u8; 32]>) -> Result<SharedSecret, ()> {
|
||||
let mut node_secret = self.get_node_secret(recipient)?;
|
||||
if let Some(tweak) = tweak {
|
||||
node_secret.mul_assign(tweak).map_err(|_| ())?;
|
||||
}
|
||||
Ok(SharedSecret::new(other_key, &node_secret))
|
||||
}
|
||||
|
||||
fn get_inbound_payment_key_material(&self) -> KeyMaterial {
|
||||
self.inbound_payment_key.clone()
|
||||
}
|
||||
|
@ -1217,6 +1232,14 @@ impl KeysInterface for PhantomKeysManager {
|
|||
}
|
||||
}
|
||||
|
||||
fn ecdh(&self, recipient: Recipient, other_key: &PublicKey, tweak: Option<&[u8; 32]>) -> Result<SharedSecret, ()> {
|
||||
let mut node_secret = self.get_node_secret(recipient)?;
|
||||
if let Some(tweak) = tweak {
|
||||
node_secret.mul_assign(tweak).map_err(|_| ())?;
|
||||
}
|
||||
Ok(SharedSecret::new(other_key, &node_secret))
|
||||
}
|
||||
|
||||
fn get_inbound_payment_key_material(&self) -> KeyMaterial {
|
||||
self.inbound_payment_key.clone()
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
//! figure out how best to make networking happen/timers fire/things get written to disk/keys get
|
||||
//! generated/etc. This makes it a good candidate for tight integration into an existing wallet
|
||||
//! instead of having a rather-separate lightning appendage to a wallet.
|
||||
//!
|
||||
//!
|
||||
//! `default` features are:
|
||||
//!
|
||||
//! * `std` - enables functionalities which require `std`, including `std::io` trait implementations and things which utilize time
|
||||
|
@ -76,6 +76,8 @@ pub mod util;
|
|||
pub mod chain;
|
||||
pub mod ln;
|
||||
pub mod routing;
|
||||
#[allow(unused)]
|
||||
mod onion_message; // To be exposed after sending/receiving OMs is supported in PeerManager.
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
/// Re-export of either `core2::io` or `std::io`, depending on the `std` feature flag.
|
||||
|
|
|
@ -6589,6 +6589,7 @@ mod tests {
|
|||
use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature};
|
||||
use bitcoin::secp256k1::ffi::Signature as FFISignature;
|
||||
use bitcoin::secp256k1::{SecretKey,PublicKey};
|
||||
use bitcoin::secp256k1::ecdh::SharedSecret;
|
||||
use bitcoin::secp256k1::ecdsa::RecoverableSignature;
|
||||
use bitcoin::hashes::sha256::Hash as Sha256;
|
||||
use bitcoin::hashes::Hash;
|
||||
|
@ -6629,6 +6630,7 @@ mod tests {
|
|||
type Signer = InMemorySigner;
|
||||
|
||||
fn get_node_secret(&self, _recipient: Recipient) -> Result<SecretKey, ()> { panic!(); }
|
||||
fn ecdh(&self, _recipient: Recipient, _other_key: &PublicKey, _tweak: Option<&[u8; 32]>) -> Result<SharedSecret, ()> { panic!(); }
|
||||
fn get_inbound_payment_key_material(&self) -> KeyMaterial { panic!(); }
|
||||
fn get_destination_script(&self) -> Script {
|
||||
let secp_ctx = Secp256k1::signing_only();
|
||||
|
|
|
@ -2192,7 +2192,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
|
|||
}
|
||||
}
|
||||
|
||||
let next_hop = match onion_utils::decode_next_hop(shared_secret, &msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac, msg.payment_hash) {
|
||||
let next_hop = match onion_utils::decode_next_payment_hop(shared_secret, &msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac, msg.payment_hash) {
|
||||
Ok(res) => res,
|
||||
Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => {
|
||||
return_malformed_err!(err_msg, err_code);
|
||||
|
@ -3153,7 +3153,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
|
|||
let phantom_secret_res = self.keys_manager.get_node_secret(Recipient::PhantomNode);
|
||||
if phantom_secret_res.is_ok() && fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, short_chan_id) {
|
||||
let phantom_shared_secret = SharedSecret::new(&onion_packet.public_key.unwrap(), &phantom_secret_res.unwrap()).secret_bytes();
|
||||
let next_hop = match onion_utils::decode_next_hop(phantom_shared_secret, &onion_packet.hop_data, onion_packet.hmac, payment_hash) {
|
||||
let next_hop = match onion_utils::decode_next_payment_hop(phantom_shared_secret, &onion_packet.hop_data, onion_packet.hmac, payment_hash) {
|
||||
Ok(res) => res,
|
||||
Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => {
|
||||
let sha256_of_onion = Sha256::hash(&onion_packet.hop_data).into_inner();
|
||||
|
|
|
@ -43,7 +43,7 @@ pub mod channel;
|
|||
#[cfg(not(fuzzing))]
|
||||
pub(crate) mod channel;
|
||||
|
||||
mod onion_utils;
|
||||
pub(crate) mod onion_utils;
|
||||
pub mod wire;
|
||||
|
||||
// Older rustc (which we support) refuses to let us call the get_payment_preimage_hash!() macro
|
||||
|
|
|
@ -31,6 +31,8 @@ use bitcoin::blockdata::script::Script;
|
|||
use bitcoin::hash_types::{Txid, BlockHash};
|
||||
|
||||
use ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures};
|
||||
use ln::onion_utils;
|
||||
use onion_message;
|
||||
|
||||
use prelude::*;
|
||||
use core::fmt;
|
||||
|
@ -40,7 +42,7 @@ use io_extras::read_to_end;
|
|||
|
||||
use util::events::MessageSendEventsProvider;
|
||||
use util::logger;
|
||||
use util::ser::{Readable, Writeable, Writer, FixedLengthReader, HighZeroBytesDroppedVarInt, Hostname};
|
||||
use util::ser::{LengthReadable, Readable, ReadableArgs, Writeable, Writer, FixedLengthReader, HighZeroBytesDroppedVarInt, Hostname};
|
||||
|
||||
use ln::{PaymentPreimage, PaymentHash, PaymentSecret};
|
||||
|
||||
|
@ -304,6 +306,14 @@ pub struct UpdateAddHTLC {
|
|||
pub(crate) onion_routing_packet: OnionPacket,
|
||||
}
|
||||
|
||||
/// An onion message to be sent or received from a peer
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct OnionMessage {
|
||||
/// Used in decrypting the onion packet's payload.
|
||||
pub blinding_point: PublicKey,
|
||||
pub(crate) onion_routing_packet: onion_message::Packet,
|
||||
}
|
||||
|
||||
/// An update_fulfill_htlc message to be sent or received from a peer
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct UpdateFulfillHTLC {
|
||||
|
@ -993,6 +1003,18 @@ pub(crate) struct OnionPacket {
|
|||
pub(crate) hmac: [u8; 32],
|
||||
}
|
||||
|
||||
impl onion_utils::Packet for OnionPacket {
|
||||
type Data = onion_utils::FixedSizeOnionPacket;
|
||||
fn new(pubkey: PublicKey, hop_data: onion_utils::FixedSizeOnionPacket, hmac: [u8; 32]) -> Self {
|
||||
Self {
|
||||
version: 0,
|
||||
public_key: Ok(pubkey),
|
||||
hop_data: hop_data.0,
|
||||
hmac,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for OnionPacket {
|
||||
fn eq(&self, other: &OnionPacket) -> bool {
|
||||
for (i, j) in self.hop_data.iter().zip(other.hop_data.iter()) {
|
||||
|
@ -1327,6 +1349,29 @@ impl_writeable_msg!(UpdateAddHTLC, {
|
|||
onion_routing_packet
|
||||
}, {});
|
||||
|
||||
impl Readable for OnionMessage {
|
||||
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
|
||||
let blinding_point: PublicKey = Readable::read(r)?;
|
||||
let len: u16 = Readable::read(r)?;
|
||||
let mut packet_reader = FixedLengthReader::new(r, len as u64);
|
||||
let onion_routing_packet: onion_message::Packet = <onion_message::Packet as LengthReadable>::read(&mut packet_reader)?;
|
||||
Ok(Self {
|
||||
blinding_point,
|
||||
onion_routing_packet,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for OnionMessage {
|
||||
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
|
||||
self.blinding_point.write(w)?;
|
||||
let onion_packet_len = self.onion_routing_packet.serialized_length();
|
||||
(onion_packet_len as u16).write(w)?;
|
||||
self.onion_routing_packet.write(w)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for FinalOnionHopData {
|
||||
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
|
||||
self.payment_secret.0.write(w)?;
|
||||
|
@ -1372,6 +1417,14 @@ impl Writeable for OnionHopData {
|
|||
}
|
||||
}
|
||||
|
||||
// ReadableArgs because we need onion_utils::decode_next_hop to accommodate payment packets and
|
||||
// onion message packets.
|
||||
impl ReadableArgs<()> for OnionHopData {
|
||||
fn read<R: Read>(r: &mut R, _arg: ()) -> Result<Self, DecodeError> {
|
||||
<Self as Readable>::read(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for OnionHopData {
|
||||
fn read<R: Read>(mut r: &mut R) -> Result<Self, DecodeError> {
|
||||
use bitcoin::consensus::encode::{Decodable, Error, VarInt};
|
||||
|
|
|
@ -15,7 +15,7 @@ use routing::gossip::NetworkUpdate;
|
|||
use routing::router::RouteHop;
|
||||
use util::chacha20::{ChaCha20, ChaChaReader};
|
||||
use util::errors::{self, APIError};
|
||||
use util::ser::{Readable, Writeable, LengthCalculatingWriter};
|
||||
use util::ser::{Readable, ReadableArgs, Writeable, LengthCalculatingWriter};
|
||||
use util::logger::Logger;
|
||||
|
||||
use bitcoin::hashes::{Hash, HashEngine};
|
||||
|
@ -30,21 +30,29 @@ use bitcoin::secp256k1;
|
|||
|
||||
use prelude::*;
|
||||
use io::{Cursor, Read};
|
||||
use core::convert::TryInto;
|
||||
use core::convert::{AsMut, TryInto};
|
||||
use core::ops::Deref;
|
||||
|
||||
pub(super) struct OnionKeys {
|
||||
pub(crate) struct OnionKeys {
|
||||
#[cfg(test)]
|
||||
pub(super) shared_secret: SharedSecret,
|
||||
pub(crate) shared_secret: SharedSecret,
|
||||
#[cfg(test)]
|
||||
pub(super) blinding_factor: [u8; 32],
|
||||
pub(super) ephemeral_pubkey: PublicKey,
|
||||
pub(super) rho: [u8; 32],
|
||||
pub(super) mu: [u8; 32],
|
||||
pub(crate) blinding_factor: [u8; 32],
|
||||
pub(crate) ephemeral_pubkey: PublicKey,
|
||||
pub(crate) rho: [u8; 32],
|
||||
pub(crate) mu: [u8; 32],
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn gen_rho_mu_from_shared_secret(shared_secret: &[u8]) -> ([u8; 32], [u8; 32]) {
|
||||
pub(crate) fn gen_rho_from_shared_secret(shared_secret: &[u8]) -> [u8; 32] {
|
||||
assert_eq!(shared_secret.len(), 32);
|
||||
let mut hmac = HmacEngine::<Sha256>::new(&[0x72, 0x68, 0x6f]); // rho
|
||||
hmac.input(&shared_secret);
|
||||
Hmac::from_engine(hmac).into_inner()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn gen_rho_mu_from_shared_secret(shared_secret: &[u8]) -> ([u8; 32], [u8; 32]) {
|
||||
assert_eq!(shared_secret.len(), 32);
|
||||
({
|
||||
let mut hmac = HmacEngine::<Sha256>::new(&[0x72, 0x68, 0x6f]); // rho
|
||||
|
@ -74,7 +82,7 @@ pub(super) fn gen_ammag_from_shared_secret(shared_secret: &[u8]) -> [u8; 32] {
|
|||
Hmac::from_engine(hmac).into_inner()
|
||||
}
|
||||
|
||||
pub(super) fn next_hop_packet_pubkey<T: secp256k1::Signing + secp256k1::Verification>(secp_ctx: &Secp256k1<T>, mut packet_pubkey: PublicKey, packet_shared_secret: &[u8; 32]) -> Result<PublicKey, secp256k1::Error> {
|
||||
pub(crate) fn next_hop_packet_pubkey<T: secp256k1::Signing + secp256k1::Verification>(secp_ctx: &Secp256k1<T>, mut packet_pubkey: PublicKey, packet_shared_secret: &[u8; 32]) -> Result<PublicKey, secp256k1::Error> {
|
||||
let blinding_factor = {
|
||||
let mut sha = Sha256::engine();
|
||||
sha.input(&packet_pubkey.serialize()[..]);
|
||||
|
@ -187,8 +195,8 @@ pub(super) fn build_onion_payloads(path: &Vec<RouteHop>, total_msat: u64, paymen
|
|||
pub(crate) const ONION_DATA_LEN: usize = 20*65;
|
||||
|
||||
#[inline]
|
||||
fn shift_arr_right(arr: &mut [u8; ONION_DATA_LEN], amt: usize) {
|
||||
for i in (amt..ONION_DATA_LEN).rev() {
|
||||
fn shift_slice_right(arr: &mut [u8], amt: usize) {
|
||||
for i in (amt..arr.len()).rev() {
|
||||
arr[i] = arr[i-amt];
|
||||
}
|
||||
for i in 0..amt {
|
||||
|
@ -210,14 +218,15 @@ pub(super) fn route_size_insane(payloads: &Vec<msgs::OnionHopData>) -> bool {
|
|||
false
|
||||
}
|
||||
|
||||
/// panics if route_size_insane(paylods)
|
||||
/// panics if route_size_insane(payloads)
|
||||
pub(super) fn construct_onion_packet(payloads: Vec<msgs::OnionHopData>, onion_keys: Vec<OnionKeys>, prng_seed: [u8; 32], associated_data: &PaymentHash) -> msgs::OnionPacket {
|
||||
let mut packet_data = [0; ONION_DATA_LEN];
|
||||
|
||||
let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]);
|
||||
chacha.process(&[0; ONION_DATA_LEN], &mut packet_data);
|
||||
|
||||
construct_onion_packet_with_init_noise(payloads, onion_keys, packet_data, associated_data)
|
||||
construct_onion_packet_with_init_noise::<_, _>(
|
||||
payloads, onion_keys, FixedSizeOnionPacket(packet_data), Some(associated_data))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -229,12 +238,50 @@ pub(super) fn construct_onion_packet_bogus_hopdata<HD: Writeable>(payloads: Vec<
|
|||
let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]);
|
||||
chacha.process(&[0; ONION_DATA_LEN], &mut packet_data);
|
||||
|
||||
construct_onion_packet_with_init_noise(payloads, onion_keys, packet_data, associated_data)
|
||||
construct_onion_packet_with_init_noise::<_, _>(
|
||||
payloads, onion_keys, FixedSizeOnionPacket(packet_data), Some(associated_data))
|
||||
}
|
||||
|
||||
/// panics if route_size_insane(paylods)
|
||||
fn construct_onion_packet_with_init_noise<HD: Writeable>(mut payloads: Vec<HD>, onion_keys: Vec<OnionKeys>, mut packet_data: [u8; ONION_DATA_LEN], associated_data: &PaymentHash) -> msgs::OnionPacket {
|
||||
/// Since onion message packets and onion payment packets have different lengths but are otherwise
|
||||
/// identical, we use this trait to allow `construct_onion_packet_with_init_noise` to return either
|
||||
/// type.
|
||||
pub(crate) trait Packet {
|
||||
type Data: AsMut<[u8]>;
|
||||
fn new(pubkey: PublicKey, hop_data: Self::Data, hmac: [u8; 32]) -> Self;
|
||||
}
|
||||
|
||||
// Needed for rustc versions older than 1.47 to avoid E0277: "arrays only have std trait
|
||||
// implementations for lengths 0..=32".
|
||||
pub(crate) struct FixedSizeOnionPacket(pub(crate) [u8; ONION_DATA_LEN]);
|
||||
|
||||
impl AsMut<[u8]> for FixedSizeOnionPacket {
|
||||
fn as_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn payloads_serialized_length<HD: Writeable>(payloads: &Vec<HD>) -> usize {
|
||||
payloads.iter().map(|p| p.serialized_length() + 32 /* HMAC */).sum()
|
||||
}
|
||||
|
||||
/// panics if payloads_serialized_length(payloads) > packet_data_len
|
||||
pub(crate) fn construct_onion_message_packet<HD: Writeable, P: Packet<Data = Vec<u8>>>(
|
||||
payloads: Vec<HD>, onion_keys: Vec<OnionKeys>, prng_seed: [u8; 32], packet_data_len: usize) -> P
|
||||
{
|
||||
let mut packet_data = vec![0; packet_data_len];
|
||||
|
||||
let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]);
|
||||
chacha.process_in_place(&mut packet_data);
|
||||
|
||||
construct_onion_packet_with_init_noise::<_, _>(payloads, onion_keys, packet_data, None)
|
||||
}
|
||||
|
||||
/// panics if payloads_serialized_length(payloads) > packet_data.len()
|
||||
fn construct_onion_packet_with_init_noise<HD: Writeable, P: Packet>(
|
||||
mut payloads: Vec<HD>, onion_keys: Vec<OnionKeys>, mut packet_data: P::Data, associated_data: Option<&PaymentHash>) -> P
|
||||
{
|
||||
let filler = {
|
||||
let packet_data = packet_data.as_mut();
|
||||
const ONION_HOP_DATA_LEN: usize = 65; // We may decrease this eventually after TLV is common
|
||||
let mut res = Vec::with_capacity(ONION_HOP_DATA_LEN * (payloads.len() - 1));
|
||||
|
||||
|
@ -243,7 +290,7 @@ fn construct_onion_packet_with_init_noise<HD: Writeable>(mut payloads: Vec<HD>,
|
|||
if i == payloads.len() - 1 { break; }
|
||||
|
||||
let mut chacha = ChaCha20::new(&keys.rho, &[0u8; 8]);
|
||||
for _ in 0..(ONION_DATA_LEN - pos) { // TODO: Batch this.
|
||||
for _ in 0..(packet_data.len() - pos) { // TODO: Batch this.
|
||||
let mut dummy = [0; 1];
|
||||
chacha.process_in_place(&mut dummy); // We don't have a seek function :(
|
||||
}
|
||||
|
@ -251,7 +298,7 @@ fn construct_onion_packet_with_init_noise<HD: Writeable>(mut payloads: Vec<HD>,
|
|||
let mut payload_len = LengthCalculatingWriter(0);
|
||||
payload.write(&mut payload_len).expect("Failed to calculate length");
|
||||
pos += payload_len.0 + 32;
|
||||
assert!(pos <= ONION_DATA_LEN);
|
||||
assert!(pos <= packet_data.len());
|
||||
|
||||
res.resize(pos, 0u8);
|
||||
chacha.process_in_place(&mut res);
|
||||
|
@ -263,29 +310,28 @@ fn construct_onion_packet_with_init_noise<HD: Writeable>(mut payloads: Vec<HD>,
|
|||
for (i, (payload, keys)) in payloads.iter_mut().zip(onion_keys.iter()).rev().enumerate() {
|
||||
let mut payload_len = LengthCalculatingWriter(0);
|
||||
payload.write(&mut payload_len).expect("Failed to calculate length");
|
||||
shift_arr_right(&mut packet_data, payload_len.0 + 32);
|
||||
|
||||
let packet_data = packet_data.as_mut();
|
||||
shift_slice_right(packet_data, payload_len.0 + 32);
|
||||
packet_data[0..payload_len.0].copy_from_slice(&payload.encode()[..]);
|
||||
packet_data[payload_len.0..(payload_len.0 + 32)].copy_from_slice(&hmac_res);
|
||||
|
||||
let mut chacha = ChaCha20::new(&keys.rho, &[0u8; 8]);
|
||||
chacha.process_in_place(&mut packet_data);
|
||||
chacha.process_in_place(packet_data);
|
||||
|
||||
if i == 0 {
|
||||
packet_data[ONION_DATA_LEN - filler.len()..ONION_DATA_LEN].copy_from_slice(&filler[..]);
|
||||
}
|
||||
|
||||
let mut hmac = HmacEngine::<Sha256>::new(&keys.mu);
|
||||
hmac.input(&packet_data);
|
||||
hmac.input(&associated_data.0[..]);
|
||||
hmac.input(packet_data);
|
||||
if let Some(associated_data) = associated_data {
|
||||
hmac.input(&associated_data.0[..]);
|
||||
}
|
||||
hmac_res = Hmac::from_engine(hmac).into_inner();
|
||||
}
|
||||
|
||||
msgs::OnionPacket {
|
||||
version: 0,
|
||||
public_key: Ok(onion_keys.first().unwrap().ephemeral_pubkey),
|
||||
hop_data: packet_data,
|
||||
hmac: hmac_res,
|
||||
}
|
||||
P::new(onion_keys.first().unwrap().ephemeral_pubkey, packet_data, hmac_res)
|
||||
}
|
||||
|
||||
/// Encrypts a failure packet. raw_packet can either be a
|
||||
|
@ -534,7 +580,50 @@ pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(secp_ctx: &
|
|||
} else { unreachable!(); }
|
||||
}
|
||||
|
||||
/// Data decrypted from the onion payload.
|
||||
/// An input used when decoding an onion packet.
|
||||
pub(crate) trait DecodeInput {
|
||||
type Arg;
|
||||
/// If Some, this is the input when checking the hmac of the onion packet.
|
||||
fn payment_hash(&self) -> Option<&PaymentHash>;
|
||||
/// Read argument when decrypting our hop payload.
|
||||
fn read_arg(self) -> Self::Arg;
|
||||
}
|
||||
|
||||
impl DecodeInput for PaymentHash {
|
||||
type Arg = ();
|
||||
fn payment_hash(&self) -> Option<&PaymentHash> {
|
||||
Some(self)
|
||||
}
|
||||
fn read_arg(self) -> Self::Arg { () }
|
||||
}
|
||||
|
||||
impl DecodeInput for SharedSecret {
|
||||
type Arg = SharedSecret;
|
||||
fn payment_hash(&self) -> Option<&PaymentHash> {
|
||||
None
|
||||
}
|
||||
fn read_arg(self) -> Self::Arg { self }
|
||||
}
|
||||
|
||||
/// Allows `decode_next_hop` to return the next hop packet bytes for either payments or onion
|
||||
/// message forwards.
|
||||
pub(crate) trait NextPacketBytes: AsMut<[u8]> {
|
||||
fn new(len: usize) -> Self;
|
||||
}
|
||||
|
||||
impl NextPacketBytes for FixedSizeOnionPacket {
|
||||
fn new(_len: usize) -> Self {
|
||||
Self([0 as u8; ONION_DATA_LEN])
|
||||
}
|
||||
}
|
||||
|
||||
impl NextPacketBytes for Vec<u8> {
|
||||
fn new(len: usize) -> Self {
|
||||
vec![0 as u8; len]
|
||||
}
|
||||
}
|
||||
|
||||
/// Data decrypted from a payment's onion payload.
|
||||
pub(crate) enum Hop {
|
||||
/// This onion payload was for us, not for forwarding to a next-hop. Contains information for
|
||||
/// verifying the incoming payment.
|
||||
|
@ -546,11 +635,12 @@ pub(crate) enum Hop {
|
|||
/// HMAC of the next hop's onion packet.
|
||||
next_hop_hmac: [u8; 32],
|
||||
/// Bytes of the onion packet we're forwarding.
|
||||
new_packet_bytes: [u8; 20*65],
|
||||
new_packet_bytes: [u8; ONION_DATA_LEN],
|
||||
},
|
||||
}
|
||||
|
||||
/// Error returned when we fail to decode the onion packet.
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum OnionDecodeErr {
|
||||
/// The HMAC of the onion packet did not match the hop data.
|
||||
Malformed {
|
||||
|
@ -564,11 +654,27 @@ pub(crate) enum OnionDecodeErr {
|
|||
},
|
||||
}
|
||||
|
||||
pub(crate) fn decode_next_hop(shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], payment_hash: PaymentHash) -> Result<Hop, OnionDecodeErr> {
|
||||
pub(crate) fn decode_next_payment_hop(shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], payment_hash: PaymentHash) -> Result<Hop, OnionDecodeErr> {
|
||||
match decode_next_hop(shared_secret, hop_data, hmac_bytes, payment_hash) {
|
||||
Ok((next_hop_data, None)) => Ok(Hop::Receive(next_hop_data)),
|
||||
Ok((next_hop_data, Some((next_hop_hmac, FixedSizeOnionPacket(new_packet_bytes))))) => {
|
||||
Ok(Hop::Forward {
|
||||
next_hop_data,
|
||||
next_hop_hmac,
|
||||
new_packet_bytes
|
||||
})
|
||||
},
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn decode_next_hop<D: DecodeInput, R: ReadableArgs<D::Arg>, N: NextPacketBytes>(shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], decode_input: D) -> Result<(R, Option<([u8; 32], N)>), OnionDecodeErr> {
|
||||
let (rho, mu) = gen_rho_mu_from_shared_secret(&shared_secret);
|
||||
let mut hmac = HmacEngine::<Sha256>::new(&mu);
|
||||
hmac.input(hop_data);
|
||||
hmac.input(&payment_hash.0[..]);
|
||||
if let Some(payment_hash) = decode_input.payment_hash() {
|
||||
hmac.input(&payment_hash.0[..]);
|
||||
}
|
||||
if !fixed_time_eq(&Hmac::from_engine(hmac).into_inner(), &hmac_bytes) {
|
||||
return Err(OnionDecodeErr::Malformed {
|
||||
err_msg: "HMAC Check failed",
|
||||
|
@ -578,7 +684,7 @@ pub(crate) fn decode_next_hop(shared_secret: [u8; 32], hop_data: &[u8], hmac_byt
|
|||
|
||||
let mut chacha = ChaCha20::new(&rho, &[0u8; 8]);
|
||||
let mut chacha_stream = ChaChaReader { chacha: &mut chacha, read: Cursor::new(&hop_data[..]) };
|
||||
match <msgs::OnionHopData as Readable>::read(&mut chacha_stream) {
|
||||
match R::read(&mut chacha_stream, decode_input.read_arg()) {
|
||||
Err(err) => {
|
||||
let error_code = match err {
|
||||
msgs::DecodeError::UnknownVersion => 0x4000 | 1, // unknown realm byte
|
||||
|
@ -616,10 +722,11 @@ pub(crate) fn decode_next_hop(shared_secret: [u8; 32], hop_data: &[u8], hmac_byt
|
|||
chacha_stream.read_exact(&mut next_bytes).unwrap();
|
||||
assert_ne!(next_bytes[..], [0; 32][..]);
|
||||
}
|
||||
return Ok(Hop::Receive(msg));
|
||||
return Ok((msg, None)); // We are the final destination for this packet
|
||||
} else {
|
||||
let mut new_packet_bytes = [0; 20*65];
|
||||
let read_pos = chacha_stream.read(&mut new_packet_bytes).unwrap();
|
||||
let mut new_packet_bytes = N::new(hop_data.len());
|
||||
let read_pos = hop_data.len() - chacha_stream.read.position() as usize;
|
||||
chacha_stream.read_exact(&mut new_packet_bytes.as_mut()[..read_pos]).unwrap();
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
// Check two things:
|
||||
|
@ -631,12 +738,8 @@ pub(crate) fn decode_next_hop(shared_secret: [u8; 32], hop_data: &[u8], hmac_byt
|
|||
}
|
||||
// Once we've emptied the set of bytes our peer gave us, encrypt 0 bytes until we
|
||||
// fill the onion hop data we'll forward to our next-hop peer.
|
||||
chacha_stream.chacha.process_in_place(&mut new_packet_bytes[read_pos..]);
|
||||
return Ok(Hop::Forward {
|
||||
next_hop_data: msg,
|
||||
next_hop_hmac: hmac,
|
||||
new_packet_bytes,
|
||||
})
|
||||
chacha_stream.chacha.process_in_place(&mut new_packet_bytes.as_mut()[read_pos..]);
|
||||
return Ok((msg, Some((hmac, new_packet_bytes)))) // This packet needs forwarding
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -775,7 +878,7 @@ mod tests {
|
|||
},
|
||||
);
|
||||
|
||||
let packet = super::construct_onion_packet_with_init_noise(payloads, onion_keys, [0; super::ONION_DATA_LEN], &PaymentHash([0x42; 32]));
|
||||
let packet: msgs::OnionPacket = super::construct_onion_packet_with_init_noise::<_, _>(payloads, onion_keys, super::FixedSizeOnionPacket([0; super::ONION_DATA_LEN]), Some(&PaymentHash([0x42; 32])));
|
||||
// Just check the final packet encoding, as it includes all the per-hop vectors in it
|
||||
// anyway...
|
||||
assert_eq!(packet.encode(), hex::decode("0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a716a996c7845c93d90e4ecbb9bde4ece2f69425c99e4bc820e44485455f135edc0d10f7d61ab590531cf08000179a333a347f8b4072f216400406bdf3bf038659793d4a1fd7b246979e3150a0a4cb052c9ec69acf0f48c3d39cd55675fe717cb7d80ce721caad69320c3a469a202f1e468c67eaf7a7cd8226d0fd32f7b48084dca885d56047694762b67021713ca673929c163ec36e04e40ca8e1c6d17569419d3039d9a1ec866abe044a9ad635778b961fc0776dc832b3a451bd5d35072d2269cf9b040f6b7a7dad84fb114ed413b1426cb96ceaf83825665ed5a1d002c1687f92465b49ed4c7f0218ff8c6c7dd7221d589c65b3b9aaa71a41484b122846c7c7b57e02e679ea8469b70e14fe4f70fee4d87b910cf144be6fe48eef24da475c0b0bcc6565ae82cd3f4e3b24c76eaa5616c6111343306ab35c1fe5ca4a77c0e314ed7dba39d6f1e0de791719c241a939cc493bea2bae1c1e932679ea94d29084278513c77b899cc98059d06a27d171b0dbdf6bee13ddc4fc17a0c4d2827d488436b57baa167544138ca2e64a11b43ac8a06cd0c2fba2d4d900ed2d9205305e2d7383cc98dacb078133de5f6fb6bed2ef26ba92cea28aafc3b9948dd9ae5559e8bd6920b8cea462aa445ca6a95e0e7ba52961b181c79e73bd581821df2b10173727a810c92b83b5ba4a0403eb710d2ca10689a35bec6c3a708e9e92f7d78ff3c5d9989574b00c6736f84c199256e76e19e78f0c98a9d580b4a658c84fc8f2096c2fbea8f5f8c59d0fdacb3be2802ef802abbecb3aba4acaac69a0e965abd8981e9896b1f6ef9d60f7a164b371af869fd0e48073742825e9434fc54da837e120266d53302954843538ea7c6c3dbfb4ff3b2fdbe244437f2a153ccf7bdb4c92aa08102d4f3cff2ae5ef86fab4653595e6a5837fa2f3e29f27a9cde5966843fb847a4a61f1e76c281fe8bb2b0a181d096100db5a1a5ce7a910238251a43ca556712eaadea167fb4d7d75825e440f3ecd782036d7574df8bceacb397abefc5f5254d2722215c53ff54af8299aaaad642c6d72a14d27882d9bbd539e1cc7a527526ba89b8c037ad09120e98ab042d3e8652b31ae0e478516bfaf88efca9f3676ffe99d2819dcaeb7610a626695f53117665d267d3f7abebd6bbd6733f645c72c389f03855bdf1e4b8075b516569b118233a0f0971d24b83113c0b096f5216a207ca99a7cddc81c130923fe3d91e7508c9ac5f2e914ff5dccab9e558566fa14efb34ac98d878580814b94b73acbfde9072f30b881f7f0fff42d4045d1ace6322d86a97d164aa84d93a60498065cc7c20e636f5862dc81531a88c60305a2e59a985be327a6902e4bed986dbf4a0b50c217af0ea7fdf9ab37f9ea1a1aaa72f54cf40154ea9b269f1a7c09f9f43245109431a175d50e2db0132337baa0ef97eed0fcf20489da36b79a1172faccc2f7ded7c60e00694282d93359c4682135642bc81f433574aa8ef0c97b4ade7ca372c5ffc23c7eddd839bab4e0f14d6df15c9dbeab176bec8b5701cf054eb3072f6dadc98f88819042bf10c407516ee58bce33fbe3b3d86a54255e577db4598e30a135361528c101683a5fcde7e8ba53f3456254be8f45fe3a56120ae96ea3773631fcb3873aa3abd91bcff00bd38bd43697a2e789e00da6077482e7b1b1a677b5afae4c54e6cbdf7377b694eb7d7a5b913476a5be923322d3de06060fd5e819635232a2cf4f0731da13b8546d1d6d4f8d75b9fce6c2341a71b0ea6f780df54bfdb0dd5cd9855179f602f9172307c7268724c3618e6817abd793adc214a0dc0bc616816632f27ea336fb56dfd").unwrap());
|
||||
|
@ -854,7 +957,7 @@ mod tests {
|
|||
}),
|
||||
);
|
||||
|
||||
let packet = super::construct_onion_packet_with_init_noise(payloads, onion_keys, [0; super::ONION_DATA_LEN], &PaymentHash([0x42; 32]));
|
||||
let packet: msgs::OnionPacket = super::construct_onion_packet_with_init_noise::<_, _>(payloads, onion_keys, super::FixedSizeOnionPacket([0; super::ONION_DATA_LEN]), Some(&PaymentHash([0x42; 32])));
|
||||
// Just check the final packet encoding, as it includes all the per-hop vectors in it
|
||||
// anyway...
|
||||
assert_eq!(packet.encode(), hex::decode("0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a71a060daf367132b378b3a3883c0e2c0e026b8900b2b5cdbc784e1a3bb913f88a9c50f7d61ab590531cf08000178a333a347f8b4072ed056f820f77774345e183a342ec4729f3d84accf515e88adddb85ecc08daba68404bae9a8e8d7178977d7094a1ae549f89338c0777551f874159eb42d3a59fb9285ad4e24883f27de23942ec966611e99bee1cee503455be9e8e642cef6cef7b9864130f692283f8a973d47a8f1c1726b6e59969385975c766e35737c8d76388b64f748ee7943ffb0e2ee45c57a1abc40762ae598723d21bd184e2b338f68ebff47219357bd19cd7e01e2337b806ef4d717888e129e59cd3dc31e6201ccb2fd6d7499836f37a993262468bcb3a4dcd03a22818aca49c6b7b9b8e9e870045631d8e039b066ff86e0d1b7291f71cefa7264c70404a8e538b566c17ccc5feab231401e6c08a01bd5edfc1aa8e3e533b96e82d1f91118d508924b923531929aea889fcdf050597c681185f336b1da63b0939aa2b7c50b21b5eb7b6ad66c81fab98a3cdf73f658149e7e9ced4edde5d38c9b8f92e16f6b4ab13d7fca6a0e4ecc9f9de611a90da6e99c39551094c56e3196f282c5dffd9fc4b2fc12f3bca8e6fe47eb45fbdd3be21a8a8d200797eae3c9a0497132f92410d804977408494dff49dd3d8bce248e0b74fd9e6f0f7102c25ddfa02bd9ad9f746abbfa337ef811d5345a9e16b60de1767b209645ba40bd1f9a5f75bc04feca9b27c5554be4fe83fac2cb83aa447a817bb85ae966c68b420063833fada375e2f515965e687a45699632902672c654d1d18d7bcbf55e8fa57f63f2da449f8e1e606e8722df081e5f193fc4179feb99ad22819afdeef211f7c54afdba92aeef0c00b7bc2b65a4813c01f907a8377585708f2d4c940a25328e585714c8ded0a9a4d7a6de1027c1cb7a0198cd3db68b58c0704dfd0cfbe624e9cd18cc0ae5d96697bb476708b9ee0403d211e64e0d5a7683a7a9a140c02f0ff1c6e67a302941b4052bdea8a63e70a3ad62c5b89c698f1fd3c7685cb49705096cad702d02d93bcb1c27a409f4c9bddec001205ca4a2740f19b50900be81c7e847f1a863deea8d35701f1355cad8db57b1d4eb2ab4e29587734785abfb46ddede71928213d7d089dfdeda052827f459f1688cc0935bd47e7bcec27427c8376dcce7e22699567c0d145f8a7db33f6758815f1f15f9f7a9760dec4f34ae095edda4c64e9735bdd029c4e32c2ee31ba47ec5e6bdb97813d52dbd15b4e0b7a2c7f790ae64104d99f38c127f0a093288fa34144adb16b8968d4fa7656fcec99de8503dd46d3b03620a71c7cd085364abd30dccf7fbda25a1cdc102600149c9af1c97aa0372cd2e1909f28ac5c686f432b310e79528c9b8b9e8f314c1e74621ce6308ad2278b81d460892e0d9dd38b7c76d58be6dfd10ae7583ee1e7ef5b3f6f78dc60af0950df1b00cc55b6d178ba2e476bea0eaeef49323b83f05804159e7aef4eed4cc60dd07be76f067dfd0bcfb0b806b69ba921336a20c43c832d0cab8fa3ddeb29e3bf07b0d98a112eb07802756235a49d44a8b82a950d84e95e01971f0e106ccb337f07384e21620e0ad39e16ed9edca123226cf55ac44f449eeb53e38a7f27d101806e4823e4efcc887414240ee6826c4a5cb1c6443ad36ebf905a435c1d9054e54173911b17b5b40f60b3d9fd5f12eac54ca1e20191f5f18544d5fd3d665e9bcef96fb44b76110aa64d9db4c86c9513cbdad546538e8aec521fbe83ceac5e74a15629f1ed0b870a1d0d1e5680b6d6100d1bd3f3b9043bd35b8919c4088f1949b8be89e4701eb870f8ed64fafa446c78df3ea").unwrap());
|
||||
|
|
153
lightning/src/onion_message/blinded_route.rs
Normal file
153
lightning/src/onion_message/blinded_route.rs
Normal file
|
@ -0,0 +1,153 @@
|
|||
// This file is Copyright its original authors, visible in version control
|
||||
// history.
|
||||
//
|
||||
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
|
||||
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
|
||||
// You may not use this file except in accordance with one or both of these
|
||||
// licenses.
|
||||
|
||||
//! Creating blinded routes and related utilities live here.
|
||||
|
||||
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
|
||||
|
||||
use chain::keysinterface::{KeysInterface, Sign};
|
||||
use super::utils;
|
||||
use util::chacha20poly1305rfc::ChaChaPolyWriteAdapter;
|
||||
use util::ser::{VecWriter, Writeable, Writer};
|
||||
|
||||
use core::iter::FromIterator;
|
||||
use io;
|
||||
use prelude::*;
|
||||
|
||||
/// Onion messages can be sent and received to blinded routes, which serve to hide the identity of
|
||||
/// the recipient.
|
||||
pub struct BlindedRoute {
|
||||
/// To send to a blinded route, the sender first finds a route to the unblinded
|
||||
/// `introduction_node_id`, which can unblind its [`encrypted_payload`] to find out the onion
|
||||
/// message's next hop and forward it along.
|
||||
///
|
||||
/// [`encrypted_payload`]: BlindedHop::encrypted_payload
|
||||
pub(super) introduction_node_id: PublicKey,
|
||||
/// Used by the introduction node to decrypt its [`encrypted_payload`] to forward the onion
|
||||
/// message.
|
||||
///
|
||||
/// [`encrypted_payload`]: BlindedHop::encrypted_payload
|
||||
pub(super) blinding_point: PublicKey,
|
||||
/// The hops composing the blinded route.
|
||||
pub(super) blinded_hops: Vec<BlindedHop>,
|
||||
}
|
||||
|
||||
/// Used to construct the blinded hops portion of a blinded route. These hops cannot be identified
|
||||
/// by outside observers and thus can be used to hide the identity of the recipient.
|
||||
pub struct BlindedHop {
|
||||
/// The blinded node id of this hop in a blinded route.
|
||||
pub(super) blinded_node_id: PublicKey,
|
||||
/// The encrypted payload intended for this hop in a blinded route.
|
||||
// The node sending to this blinded route will later encode this payload into the onion packet for
|
||||
// this hop.
|
||||
pub(super) encrypted_payload: Vec<u8>,
|
||||
}
|
||||
|
||||
impl BlindedRoute {
|
||||
/// Create a blinded route to be forwarded along `node_pks`. The last node pubkey in `node_pks`
|
||||
/// will be the destination node.
|
||||
///
|
||||
/// Errors if less than two hops are provided or if `node_pk`(s) are invalid.
|
||||
// TODO: make all payloads the same size with padding + add dummy hops
|
||||
pub fn new<Signer: Sign, K: KeysInterface, T: secp256k1::Signing + secp256k1::Verification>
|
||||
(node_pks: &[PublicKey], keys_manager: &K, secp_ctx: &Secp256k1<T>) -> Result<Self, ()>
|
||||
{
|
||||
if node_pks.len() < 2 { return Err(()) }
|
||||
let blinding_secret_bytes = keys_manager.get_secure_random_bytes();
|
||||
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
|
||||
let introduction_node_id = node_pks[0];
|
||||
|
||||
Ok(BlindedRoute {
|
||||
introduction_node_id,
|
||||
blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
|
||||
blinded_hops: blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct blinded hops for the given `unblinded_path`.
|
||||
fn blinded_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)
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// 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 route use this value to concatenate the route they find to the
|
||||
/// introduction node with the blinded route.
|
||||
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 route that this onion message is
|
||||
/// sending to. This is useful for receivers to check that said blinded route 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(())
|
||||
}
|
||||
}
|
142
lightning/src/onion_message/functional_tests.rs
Normal file
142
lightning/src/onion_message/functional_tests.rs
Normal file
|
@ -0,0 +1,142 @@
|
|||
// This file is Copyright its original authors, visible in version control
|
||||
// history.
|
||||
//
|
||||
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
|
||||
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
|
||||
// You may not use this file except in accordance with one or both of these
|
||||
// licenses.
|
||||
|
||||
//! Onion message testing and test utilities live here.
|
||||
|
||||
use chain::keysinterface::{KeysInterface, Recipient};
|
||||
use super::{BlindedRoute, Destination, OnionMessenger, SendError};
|
||||
use util::enforcing_trait_impls::EnforcingSigner;
|
||||
use util::test_utils;
|
||||
|
||||
use bitcoin::network::constants::Network;
|
||||
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
|
||||
|
||||
use sync::Arc;
|
||||
|
||||
struct MessengerNode {
|
||||
keys_manager: Arc<test_utils::TestKeysInterface>,
|
||||
messenger: OnionMessenger<EnforcingSigner, Arc<test_utils::TestKeysInterface>, Arc<test_utils::TestLogger>>,
|
||||
logger: Arc<test_utils::TestLogger>,
|
||||
}
|
||||
|
||||
impl MessengerNode {
|
||||
fn get_node_pk(&self) -> PublicKey {
|
||||
let secp_ctx = Secp256k1::new();
|
||||
PublicKey::from_secret_key(&secp_ctx, &self.keys_manager.get_node_secret(Recipient::Node).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
fn create_nodes(num_messengers: u8) -> Vec<MessengerNode> {
|
||||
let mut res = Vec::new();
|
||||
for i in 0..num_messengers {
|
||||
let logger = Arc::new(test_utils::TestLogger::with_id(format!("node {}", i)));
|
||||
let seed = [i as u8; 32];
|
||||
let keys_manager = Arc::new(test_utils::TestKeysInterface::new(&seed, Network::Testnet));
|
||||
res.push(MessengerNode {
|
||||
keys_manager: keys_manager.clone(),
|
||||
messenger: OnionMessenger::new(keys_manager, logger.clone()),
|
||||
logger,
|
||||
});
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn pass_along_path(mut path: Vec<MessengerNode>, expected_path_id: Option<[u8; 32]>) {
|
||||
let mut prev_node = path.remove(0);
|
||||
let num_nodes = path.len();
|
||||
for (idx, node) in path.into_iter().enumerate() {
|
||||
let events = prev_node.messenger.release_pending_msgs();
|
||||
assert_eq!(events.len(), 1);
|
||||
let onion_msg = {
|
||||
let msgs = events.get(&node.get_node_pk()).unwrap();
|
||||
assert_eq!(msgs.len(), 1);
|
||||
msgs[0].clone()
|
||||
};
|
||||
node.messenger.handle_onion_message(&prev_node.get_node_pk(), &onion_msg);
|
||||
if idx == num_nodes - 1 {
|
||||
node.logger.assert_log_contains(
|
||||
"lightning::onion_message::messenger".to_string(),
|
||||
format!("Received an onion message with path_id: {:02x?}", expected_path_id).to_string(), 1);
|
||||
}
|
||||
prev_node = node;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_hop() {
|
||||
let nodes = create_nodes(2);
|
||||
|
||||
nodes[0].messenger.send_onion_message(&[], Destination::Node(nodes[1].get_node_pk())).unwrap();
|
||||
pass_along_path(nodes, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_unblinded_hops() {
|
||||
let nodes = create_nodes(3);
|
||||
|
||||
nodes[0].messenger.send_onion_message(&[nodes[1].get_node_pk()], Destination::Node(nodes[2].get_node_pk())).unwrap();
|
||||
pass_along_path(nodes, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_unblinded_two_blinded() {
|
||||
let nodes = create_nodes(5);
|
||||
|
||||
let secp_ctx = Secp256k1::new();
|
||||
let blinded_route = BlindedRoute::new::<EnforcingSigner, _, _>(&[nodes[3].get_node_pk(), nodes[4].get_node_pk()], &*nodes[4].keys_manager, &secp_ctx).unwrap();
|
||||
|
||||
nodes[0].messenger.send_onion_message(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], Destination::BlindedRoute(blinded_route)).unwrap();
|
||||
pass_along_path(nodes, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn three_blinded_hops() {
|
||||
let nodes = create_nodes(4);
|
||||
|
||||
let secp_ctx = Secp256k1::new();
|
||||
let blinded_route = BlindedRoute::new::<EnforcingSigner, _, _>(&[nodes[1].get_node_pk(), nodes[2].get_node_pk(), nodes[3].get_node_pk()], &*nodes[3].keys_manager, &secp_ctx).unwrap();
|
||||
|
||||
nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route)).unwrap();
|
||||
pass_along_path(nodes, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_big_packet_error() {
|
||||
// Make sure we error as expected if a packet is too big to send.
|
||||
let nodes = create_nodes(1);
|
||||
|
||||
let hop_secret = SecretKey::from_slice(&hex::decode("0101010101010101010101010101010101010101010101010101010101010101").unwrap()[..]).unwrap();
|
||||
let secp_ctx = Secp256k1::new();
|
||||
let hop_node_id = PublicKey::from_secret_key(&secp_ctx, &hop_secret);
|
||||
|
||||
let hops = [hop_node_id; 400];
|
||||
let err = nodes[0].messenger.send_onion_message(&hops, Destination::Node(hop_node_id)).unwrap_err();
|
||||
assert_eq!(err, SendError::TooBigPacket);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_blinded_route_error() {
|
||||
// Make sure we error as expected if a provided blinded route has 0 or 1 hops.
|
||||
let mut nodes = create_nodes(3);
|
||||
let (node1, node2, node3) = (nodes.remove(0), nodes.remove(0), nodes.remove(0));
|
||||
|
||||
// 0 hops
|
||||
let secp_ctx = Secp256k1::new();
|
||||
let mut blinded_route = BlindedRoute::new::<EnforcingSigner, _, _>(&[node2.get_node_pk(), node3.get_node_pk()], &*node3.keys_manager, &secp_ctx).unwrap();
|
||||
blinded_route.blinded_hops.clear();
|
||||
let err = node1.messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route)).unwrap_err();
|
||||
assert_eq!(err, SendError::TooFewBlindedHops);
|
||||
|
||||
// 1 hop
|
||||
let mut blinded_route = BlindedRoute::new::<EnforcingSigner, _, _>(&[node2.get_node_pk(), node3.get_node_pk()], &*node3.keys_manager, &secp_ctx).unwrap();
|
||||
blinded_route.blinded_hops.remove(0);
|
||||
assert_eq!(blinded_route.blinded_hops.len(), 1);
|
||||
let err = node1.messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route)).unwrap_err();
|
||||
assert_eq!(err, SendError::TooFewBlindedHops);
|
||||
}
|
383
lightning/src/onion_message/messenger.rs
Normal file
383
lightning/src/onion_message/messenger.rs
Normal file
|
@ -0,0 +1,383 @@
|
|||
// This file is Copyright its original authors, visible in version control
|
||||
// history.
|
||||
//
|
||||
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
|
||||
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
|
||||
// You may not use this file except in accordance with one or both of these
|
||||
// licenses.
|
||||
|
||||
//! LDK sends, receives, and forwards onion messages via the [`OnionMessenger`]. See its docs for
|
||||
//! more information.
|
||||
|
||||
use bitcoin::hashes::{Hash, HashEngine};
|
||||
use bitcoin::hashes::hmac::{Hmac, HmacEngine};
|
||||
use bitcoin::hashes::sha256::Hash as Sha256;
|
||||
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
|
||||
|
||||
use chain::keysinterface::{InMemorySigner, KeysInterface, KeysManager, Recipient, Sign};
|
||||
use ln::msgs;
|
||||
use ln::onion_utils;
|
||||
use super::blinded_route::{BlindedRoute, ForwardTlvs, ReceiveTlvs};
|
||||
use super::packet::{BIG_PACKET_HOP_DATA_LEN, ForwardControlTlvs, Packet, Payload, ReceiveControlTlvs, SMALL_PACKET_HOP_DATA_LEN};
|
||||
use super::utils;
|
||||
use util::logger::Logger;
|
||||
|
||||
use core::ops::Deref;
|
||||
use sync::{Arc, Mutex};
|
||||
use prelude::*;
|
||||
|
||||
/// A sender, receiver and forwarder of onion messages. In upcoming releases, this object will be
|
||||
/// used to retrieve invoices and fulfill invoice requests from [offers]. Currently, only sending
|
||||
/// and receiving empty onion messages is supported.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
// Needs to be `ignore` until the `onion_message` module is made public, otherwise this is a test
|
||||
// failure.
|
||||
/// ```ignore
|
||||
/// # extern crate bitcoin;
|
||||
/// # use bitcoin::hashes::_export::_core::time::Duration;
|
||||
/// # use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
|
||||
/// # use lightning::chain::keysinterface::{InMemorySigner, KeysManager, KeysInterface};
|
||||
/// # use lightning::onion_message::{BlindedRoute, Destination, OnionMessenger};
|
||||
/// # use lightning::util::logger::{Logger, Record};
|
||||
/// # use std::sync::Arc;
|
||||
/// # struct FakeLogger {};
|
||||
/// # impl Logger for FakeLogger {
|
||||
/// # fn log(&self, record: &Record) { unimplemented!() }
|
||||
/// # }
|
||||
/// # let seed = [42u8; 32];
|
||||
/// # let time = Duration::from_secs(123456);
|
||||
/// # let keys_manager = KeysManager::new(&seed, time.as_secs(), time.subsec_nanos());
|
||||
/// # let logger = Arc::new(FakeLogger {});
|
||||
/// # let node_secret = SecretKey::from_slice(&hex::decode("0101010101010101010101010101010101010101010101010101010101010101").unwrap()[..]).unwrap();
|
||||
/// # let secp_ctx = Secp256k1::new();
|
||||
/// # let hop_node_id1 = PublicKey::from_secret_key(&secp_ctx, &node_secret);
|
||||
/// # let (hop_node_id2, hop_node_id3, hop_node_id4) = (hop_node_id1, hop_node_id1,
|
||||
/// hop_node_id1);
|
||||
/// # let destination_node_id = hop_node_id1;
|
||||
/// #
|
||||
/// // Create the onion messenger. This must use the same `keys_manager` as is passed to your
|
||||
/// // ChannelManager.
|
||||
/// let onion_messenger = OnionMessenger::new(&keys_manager, logger);
|
||||
///
|
||||
/// // Send an empty onion message to a node id.
|
||||
/// let intermediate_hops = [hop_node_id1, hop_node_id2];
|
||||
/// onion_messenger.send_onion_message(&intermediate_hops, Destination::Node(destination_node_id));
|
||||
///
|
||||
/// // Create a blinded route to yourself, for someone to send an onion message to.
|
||||
/// # let your_node_id = hop_node_id1;
|
||||
/// let hops = [hop_node_id3, hop_node_id4, your_node_id];
|
||||
/// let blinded_route = BlindedRoute::new::<InMemorySigner, _, _>(&hops, &keys_manager, &secp_ctx).unwrap();
|
||||
///
|
||||
/// // Send an empty onion message to a blinded route.
|
||||
/// # let intermediate_hops = [hop_node_id1, hop_node_id2];
|
||||
/// onion_messenger.send_onion_message(&intermediate_hops, Destination::BlindedRoute(blinded_route));
|
||||
/// ```
|
||||
///
|
||||
/// [offers]: <https://github.com/lightning/bolts/pull/798>
|
||||
/// [`OnionMessenger`]: crate::onion_message::OnionMessenger
|
||||
pub struct OnionMessenger<Signer: Sign, K: Deref, L: Deref>
|
||||
where K::Target: KeysInterface<Signer = Signer>,
|
||||
L::Target: Logger,
|
||||
{
|
||||
keys_manager: K,
|
||||
logger: L,
|
||||
pending_messages: Mutex<HashMap<PublicKey, Vec<msgs::OnionMessage>>>,
|
||||
secp_ctx: Secp256k1<secp256k1::All>,
|
||||
// Coming soon:
|
||||
// invoice_handler: InvoiceHandler,
|
||||
// custom_handler: CustomHandler, // handles custom onion messages
|
||||
}
|
||||
|
||||
/// The destination of an onion message.
|
||||
pub enum Destination {
|
||||
/// We're sending this onion message to a node.
|
||||
Node(PublicKey),
|
||||
/// We're sending this onion message to a blinded route.
|
||||
BlindedRoute(BlindedRoute),
|
||||
}
|
||||
|
||||
impl Destination {
|
||||
pub(super) fn num_hops(&self) -> usize {
|
||||
match self {
|
||||
Destination::Node(_) => 1,
|
||||
Destination::BlindedRoute(BlindedRoute { blinded_hops, .. }) => blinded_hops.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that may occur when [sending an onion message].
|
||||
///
|
||||
/// [sending an onion message]: OnionMessenger::send_onion_message
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum SendError {
|
||||
/// Errored computing onion message packet keys.
|
||||
Secp256k1(secp256k1::Error),
|
||||
/// Because implementations such as Eclair will drop onion messages where the message packet
|
||||
/// exceeds 32834 bytes, we refuse to send messages where the packet exceeds this size.
|
||||
TooBigPacket,
|
||||
/// The provided [`Destination`] was an invalid [`BlindedRoute`], due to having fewer than two
|
||||
/// blinded hops.
|
||||
TooFewBlindedHops,
|
||||
}
|
||||
|
||||
impl<Signer: Sign, K: Deref, L: Deref> OnionMessenger<Signer, K, L>
|
||||
where K::Target: KeysInterface<Signer = Signer>,
|
||||
L::Target: Logger,
|
||||
{
|
||||
/// Constructs a new `OnionMessenger` to send, forward, and delegate received onion messages to
|
||||
/// their respective handlers.
|
||||
pub fn new(keys_manager: K, logger: L) -> Self {
|
||||
let mut secp_ctx = Secp256k1::new();
|
||||
secp_ctx.seeded_randomize(&keys_manager.get_secure_random_bytes());
|
||||
OnionMessenger {
|
||||
keys_manager,
|
||||
pending_messages: Mutex::new(HashMap::new()),
|
||||
secp_ctx,
|
||||
logger,
|
||||
}
|
||||
}
|
||||
|
||||
/// Send an empty onion message to `destination`, routing it through `intermediate_nodes`.
|
||||
/// See [`OnionMessenger`] for example usage.
|
||||
pub fn send_onion_message(&self, intermediate_nodes: &[PublicKey], destination: Destination) -> Result<(), SendError> {
|
||||
if let Destination::BlindedRoute(BlindedRoute { ref blinded_hops, .. }) = destination {
|
||||
if blinded_hops.len() < 2 {
|
||||
return Err(SendError::TooFewBlindedHops);
|
||||
}
|
||||
}
|
||||
let blinding_secret_bytes = self.keys_manager.get_secure_random_bytes();
|
||||
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
|
||||
let (introduction_node_id, blinding_point) = if intermediate_nodes.len() != 0 {
|
||||
(intermediate_nodes[0], PublicKey::from_secret_key(&self.secp_ctx, &blinding_secret))
|
||||
} else {
|
||||
match destination {
|
||||
Destination::Node(pk) => (pk, PublicKey::from_secret_key(&self.secp_ctx, &blinding_secret)),
|
||||
Destination::BlindedRoute(BlindedRoute { introduction_node_id, blinding_point, .. }) =>
|
||||
(introduction_node_id, blinding_point),
|
||||
}
|
||||
};
|
||||
let (packet_payloads, packet_keys) = packet_payloads_and_keys(
|
||||
&self.secp_ctx, intermediate_nodes, destination, &blinding_secret)
|
||||
.map_err(|e| SendError::Secp256k1(e))?;
|
||||
|
||||
let prng_seed = self.keys_manager.get_secure_random_bytes();
|
||||
let onion_packet = construct_onion_message_packet(
|
||||
packet_payloads, packet_keys, prng_seed).map_err(|()| SendError::TooBigPacket)?;
|
||||
|
||||
let mut pending_per_peer_msgs = self.pending_messages.lock().unwrap();
|
||||
let pending_msgs = pending_per_peer_msgs.entry(introduction_node_id).or_insert(Vec::new());
|
||||
pending_msgs.push(
|
||||
msgs::OnionMessage {
|
||||
blinding_point,
|
||||
onion_routing_packet: onion_packet,
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle an incoming onion message. Currently, if a message was destined for us we will log, but
|
||||
/// soon we'll delegate the onion message to a handler that can generate invoices or send
|
||||
/// payments.
|
||||
pub fn handle_onion_message(&self, _peer_node_id: &PublicKey, msg: &msgs::OnionMessage) {
|
||||
let control_tlvs_ss = match self.keys_manager.ecdh(Recipient::Node, &msg.blinding_point, None) {
|
||||
Ok(ss) => ss,
|
||||
Err(e) => {
|
||||
log_error!(self.logger, "Failed to retrieve node secret: {:?}", e);
|
||||
return
|
||||
}
|
||||
};
|
||||
let onion_decode_ss = {
|
||||
let blinding_factor = {
|
||||
let mut hmac = HmacEngine::<Sha256>::new(b"blinded_node_id");
|
||||
hmac.input(control_tlvs_ss.as_ref());
|
||||
Hmac::from_engine(hmac).into_inner()
|
||||
};
|
||||
match self.keys_manager.ecdh(Recipient::Node, &msg.onion_routing_packet.public_key,
|
||||
Some(&blinding_factor))
|
||||
{
|
||||
Ok(ss) => ss.secret_bytes(),
|
||||
Err(()) => {
|
||||
log_trace!(self.logger, "Failed to compute onion packet shared secret");
|
||||
return
|
||||
}
|
||||
}
|
||||
};
|
||||
match onion_utils::decode_next_hop(onion_decode_ss, &msg.onion_routing_packet.hop_data[..],
|
||||
msg.onion_routing_packet.hmac, control_tlvs_ss)
|
||||
{
|
||||
Ok((Payload::Receive {
|
||||
control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { path_id })
|
||||
}, None)) => {
|
||||
log_info!(self.logger, "Received an onion message with path_id: {:02x?}", path_id);
|
||||
},
|
||||
Ok((Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs {
|
||||
next_node_id, next_blinding_override
|
||||
})), Some((next_hop_hmac, new_packet_bytes)))) => {
|
||||
// TODO: we need to check whether `next_node_id` is our node, in which case this is a dummy
|
||||
// blinded hop and this onion message is destined for us. In this situation, we should keep
|
||||
// unwrapping the onion layers to get to the final payload. Since we don't have the option
|
||||
// of creating blinded routes with dummy hops currently, we should be ok to not handle this
|
||||
// for now.
|
||||
let new_pubkey = match onion_utils::next_hop_packet_pubkey(&self.secp_ctx, msg.onion_routing_packet.public_key, &onion_decode_ss) {
|
||||
Ok(pk) => pk,
|
||||
Err(e) => {
|
||||
log_trace!(self.logger, "Failed to compute next hop packet pubkey: {}", e);
|
||||
return
|
||||
}
|
||||
};
|
||||
let outgoing_packet = Packet {
|
||||
version: 0,
|
||||
public_key: new_pubkey,
|
||||
hop_data: new_packet_bytes,
|
||||
hmac: next_hop_hmac,
|
||||
};
|
||||
|
||||
let mut pending_per_peer_msgs = self.pending_messages.lock().unwrap();
|
||||
let pending_msgs = pending_per_peer_msgs.entry(next_node_id).or_insert(Vec::new());
|
||||
pending_msgs.push(
|
||||
msgs::OnionMessage {
|
||||
blinding_point: match next_blinding_override {
|
||||
Some(blinding_point) => blinding_point,
|
||||
None => {
|
||||
let blinding_factor = {
|
||||
let mut sha = Sha256::engine();
|
||||
sha.input(&msg.blinding_point.serialize()[..]);
|
||||
sha.input(control_tlvs_ss.as_ref());
|
||||
Sha256::from_engine(sha).into_inner()
|
||||
};
|
||||
let mut next_blinding_point = msg.blinding_point;
|
||||
if let Err(e) = next_blinding_point.mul_assign(&self.secp_ctx, &blinding_factor[..]) {
|
||||
log_trace!(self.logger, "Failed to compute next blinding point: {}", e);
|
||||
return
|
||||
}
|
||||
next_blinding_point
|
||||
},
|
||||
},
|
||||
onion_routing_packet: outgoing_packet,
|
||||
},
|
||||
);
|
||||
},
|
||||
Err(e) => {
|
||||
log_trace!(self.logger, "Errored decoding onion message packet: {:?}", e);
|
||||
},
|
||||
_ => {
|
||||
log_trace!(self.logger, "Received bogus onion message packet, either the sender encoded a final hop as a forwarding hop or vice versa");
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(super) fn release_pending_msgs(&self) -> HashMap<PublicKey, Vec<msgs::OnionMessage>> {
|
||||
let mut pending_msgs = self.pending_messages.lock().unwrap();
|
||||
let mut msgs = HashMap::new();
|
||||
core::mem::swap(&mut *pending_msgs, &mut msgs);
|
||||
msgs
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: parameterize the below Simple* types with OnionMessenger and handle the messages it
|
||||
// produces
|
||||
/// Useful for simplifying the parameters of [`SimpleArcChannelManager`] and
|
||||
/// [`SimpleArcPeerManager`]. See their docs for more details.
|
||||
///
|
||||
///[`SimpleArcChannelManager`]: crate::ln::channelmanager::SimpleArcChannelManager
|
||||
///[`SimpleArcPeerManager`]: crate::ln::peer_handler::SimpleArcPeerManager
|
||||
pub type SimpleArcOnionMessenger<L> = OnionMessenger<InMemorySigner, Arc<KeysManager>, Arc<L>>;
|
||||
/// Useful for simplifying the parameters of [`SimpleRefChannelManager`] and
|
||||
/// [`SimpleRefPeerManager`]. See their docs for more details.
|
||||
///
|
||||
///[`SimpleRefChannelManager`]: crate::ln::channelmanager::SimpleRefChannelManager
|
||||
///[`SimpleRefPeerManager`]: crate::ln::peer_handler::SimpleRefPeerManager
|
||||
pub type SimpleRefOnionMessenger<'a, 'b, L> = OnionMessenger<InMemorySigner, &'a KeysManager, &'b L>;
|
||||
|
||||
/// Construct onion packet payloads and keys for sending an onion message along the given
|
||||
/// `unblinded_path` to the given `destination`.
|
||||
fn packet_payloads_and_keys<T: secp256k1::Signing + secp256k1::Verification>(
|
||||
secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], destination: Destination, session_priv: &SecretKey
|
||||
) -> Result<(Vec<(Payload, [u8; 32])>, Vec<onion_utils::OnionKeys>), secp256k1::Error> {
|
||||
let num_hops = unblinded_path.len() + destination.num_hops();
|
||||
let mut payloads = Vec::with_capacity(num_hops);
|
||||
let mut onion_packet_keys = Vec::with_capacity(num_hops);
|
||||
|
||||
let (mut intro_node_id_blinding_pt, num_blinded_hops) = if let Destination::BlindedRoute(BlindedRoute {
|
||||
introduction_node_id, blinding_point, blinded_hops }) = &destination {
|
||||
(Some((*introduction_node_id, *blinding_point)), blinded_hops.len()) } else { (None, 0) };
|
||||
let num_unblinded_hops = num_hops - num_blinded_hops;
|
||||
|
||||
let mut unblinded_path_idx = 0;
|
||||
let mut blinded_path_idx = 0;
|
||||
let mut prev_control_tlvs_ss = None;
|
||||
utils::construct_keys_callback(secp_ctx, unblinded_path, Some(destination), session_priv, |_, onion_packet_ss, ephemeral_pubkey, control_tlvs_ss, unblinded_pk_opt, enc_payload_opt| {
|
||||
if num_unblinded_hops != 0 && unblinded_path_idx < num_unblinded_hops {
|
||||
if let Some(ss) = prev_control_tlvs_ss.take() {
|
||||
payloads.push((Payload::Forward(ForwardControlTlvs::Unblinded(
|
||||
ForwardTlvs {
|
||||
next_node_id: unblinded_pk_opt.unwrap(),
|
||||
next_blinding_override: None,
|
||||
}
|
||||
)), ss));
|
||||
}
|
||||
prev_control_tlvs_ss = Some(control_tlvs_ss);
|
||||
unblinded_path_idx += 1;
|
||||
} else if let Some((intro_node_id, blinding_pt)) = intro_node_id_blinding_pt.take() {
|
||||
if let Some(control_tlvs_ss) = prev_control_tlvs_ss.take() {
|
||||
payloads.push((Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs {
|
||||
next_node_id: intro_node_id,
|
||||
next_blinding_override: Some(blinding_pt),
|
||||
})), control_tlvs_ss));
|
||||
}
|
||||
if let Some(encrypted_payload) = enc_payload_opt {
|
||||
payloads.push((Payload::Forward(ForwardControlTlvs::Blinded(encrypted_payload)),
|
||||
control_tlvs_ss));
|
||||
} else { debug_assert!(false); }
|
||||
blinded_path_idx += 1;
|
||||
} else if blinded_path_idx < num_blinded_hops - 1 && enc_payload_opt.is_some() {
|
||||
payloads.push((Payload::Forward(ForwardControlTlvs::Blinded(enc_payload_opt.unwrap())),
|
||||
control_tlvs_ss));
|
||||
blinded_path_idx += 1;
|
||||
} else if let Some(encrypted_payload) = enc_payload_opt {
|
||||
payloads.push((Payload::Receive {
|
||||
control_tlvs: ReceiveControlTlvs::Blinded(encrypted_payload),
|
||||
}, control_tlvs_ss));
|
||||
}
|
||||
|
||||
let (rho, mu) = onion_utils::gen_rho_mu_from_shared_secret(onion_packet_ss.as_ref());
|
||||
onion_packet_keys.push(onion_utils::OnionKeys {
|
||||
#[cfg(test)]
|
||||
shared_secret: onion_packet_ss,
|
||||
#[cfg(test)]
|
||||
blinding_factor: [0; 32],
|
||||
ephemeral_pubkey,
|
||||
rho,
|
||||
mu,
|
||||
});
|
||||
})?;
|
||||
|
||||
if let Some(control_tlvs_ss) = prev_control_tlvs_ss {
|
||||
payloads.push((Payload::Receive {
|
||||
control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { path_id: None, })
|
||||
}, control_tlvs_ss));
|
||||
}
|
||||
|
||||
Ok((payloads, onion_packet_keys))
|
||||
}
|
||||
|
||||
/// Errors if the serialized payload size exceeds onion_message::BIG_PACKET_HOP_DATA_LEN
|
||||
fn construct_onion_message_packet(payloads: Vec<(Payload, [u8; 32])>, onion_keys: Vec<onion_utils::OnionKeys>, prng_seed: [u8; 32]) -> Result<Packet, ()> {
|
||||
// Spec rationale:
|
||||
// "`len` allows larger messages to be sent than the standard 1300 bytes allowed for an HTLC
|
||||
// onion, but this should be used sparingly as it is reduces anonymity set, hence the
|
||||
// recommendation that it either look like an HTLC onion, or if larger, be a fixed size."
|
||||
let payloads_ser_len = onion_utils::payloads_serialized_length(&payloads);
|
||||
let hop_data_len = if payloads_ser_len <= SMALL_PACKET_HOP_DATA_LEN {
|
||||
SMALL_PACKET_HOP_DATA_LEN
|
||||
} else if payloads_ser_len <= BIG_PACKET_HOP_DATA_LEN {
|
||||
BIG_PACKET_HOP_DATA_LEN
|
||||
} else { return Err(()) };
|
||||
|
||||
Ok(onion_utils::construct_onion_message_packet::<_, _>(
|
||||
payloads, onion_keys, prng_seed, hop_data_len))
|
||||
}
|
33
lightning/src/onion_message/mod.rs
Normal file
33
lightning/src/onion_message/mod.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
// This file is Copyright its original authors, visible in version control
|
||||
// history.
|
||||
//
|
||||
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
|
||||
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
|
||||
// You may not use this file except in accordance with one or both of these
|
||||
// licenses.
|
||||
|
||||
//! Onion Messages: sending, receiving, forwarding, and ancillary utilities live here
|
||||
//!
|
||||
//! Onion messages are multi-purpose messages sent between peers over the lightning network. In the
|
||||
//! near future, they will be used to communicate invoices for [offers], unlocking use cases such as
|
||||
//! static invoices, refunds and proof of payer. Further, you will be able to accept payments
|
||||
//! without revealing your node id through the use of [blinded routes].
|
||||
//!
|
||||
//! LDK sends and receives onion messages via the [`OnionMessenger`]. See its documentation for more
|
||||
//! information on its usage.
|
||||
//!
|
||||
//! [offers]: <https://github.com/lightning/bolts/pull/798>
|
||||
//! [blinded routes]: crate::onion_message::BlindedRoute
|
||||
|
||||
mod blinded_route;
|
||||
mod messenger;
|
||||
mod packet;
|
||||
mod utils;
|
||||
#[cfg(test)]
|
||||
mod functional_tests;
|
||||
|
||||
// Re-export structs so they can be imported with just the `onion_message::` module prefix.
|
||||
pub use self::blinded_route::{BlindedRoute, BlindedHop};
|
||||
pub use self::messenger::{Destination, OnionMessenger, SendError, SimpleArcOnionMessenger, SimpleRefOnionMessenger};
|
||||
pub(crate) use self::packet::Packet;
|
250
lightning/src/onion_message/packet.rs
Normal file
250
lightning/src/onion_message/packet.rs
Normal file
|
@ -0,0 +1,250 @@
|
|||
// This file is Copyright its original authors, visible in version control
|
||||
// history.
|
||||
//
|
||||
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
|
||||
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
|
||||
// You may not use this file except in accordance with one or both of these
|
||||
// licenses.
|
||||
|
||||
//! Structs and enums useful for constructing and reading an onion message packet.
|
||||
|
||||
use bitcoin::secp256k1::PublicKey;
|
||||
use bitcoin::secp256k1::ecdh::SharedSecret;
|
||||
|
||||
use ln::msgs::DecodeError;
|
||||
use ln::onion_utils;
|
||||
use super::blinded_route::{ForwardTlvs, ReceiveTlvs};
|
||||
use util::chacha20poly1305rfc::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter};
|
||||
use util::ser::{FixedLengthReader, LengthRead, LengthReadable, LengthReadableArgs, Readable, ReadableArgs, Writeable, Writer};
|
||||
|
||||
use core::cmp;
|
||||
use io::{self, Read};
|
||||
use prelude::*;
|
||||
|
||||
// Per the spec, an onion message packet's `hop_data` field length should be
|
||||
// SMALL_PACKET_HOP_DATA_LEN if it fits, else BIG_PACKET_HOP_DATA_LEN if it fits.
|
||||
pub(super) const SMALL_PACKET_HOP_DATA_LEN: usize = 1300;
|
||||
pub(super) const BIG_PACKET_HOP_DATA_LEN: usize = 32768;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub(crate) struct Packet {
|
||||
pub(super) version: u8,
|
||||
pub(super) public_key: PublicKey,
|
||||
// Unlike the onion packets used for payments, onion message packets can have payloads greater
|
||||
// than 1300 bytes.
|
||||
// TODO: if 1300 ends up being the most common size, optimize this to be:
|
||||
// enum { ThirteenHundred([u8; 1300]), VarLen(Vec<u8>) }
|
||||
pub(super) hop_data: Vec<u8>,
|
||||
pub(super) hmac: [u8; 32],
|
||||
}
|
||||
|
||||
impl onion_utils::Packet for Packet {
|
||||
type Data = Vec<u8>;
|
||||
fn new(public_key: PublicKey, hop_data: Vec<u8>, hmac: [u8; 32]) -> Packet {
|
||||
Self {
|
||||
version: 0,
|
||||
public_key,
|
||||
hop_data,
|
||||
hmac,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for Packet {
|
||||
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
|
||||
self.version.write(w)?;
|
||||
self.public_key.write(w)?;
|
||||
w.write_all(&self.hop_data)?;
|
||||
self.hmac.write(w)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl LengthReadable for Packet {
|
||||
fn read<R: LengthRead>(r: &mut R) -> Result<Self, DecodeError> {
|
||||
const READ_BUFFER_SIZE: usize = 4096;
|
||||
|
||||
let version = Readable::read(r)?;
|
||||
let public_key = Readable::read(r)?;
|
||||
|
||||
let mut hop_data = Vec::new();
|
||||
let hop_data_len = r.total_bytes() as usize - 66; // 1 (version) + 33 (pubkey) + 32 (HMAC) = 66
|
||||
let mut read_idx = 0;
|
||||
while read_idx < hop_data_len {
|
||||
let mut read_buffer = [0; READ_BUFFER_SIZE];
|
||||
let read_amt = cmp::min(hop_data_len - read_idx, READ_BUFFER_SIZE);
|
||||
r.read_exact(&mut read_buffer[..read_amt]);
|
||||
hop_data.extend_from_slice(&read_buffer[..read_amt]);
|
||||
read_idx += read_amt;
|
||||
}
|
||||
|
||||
let hmac = Readable::read(r)?;
|
||||
Ok(Packet {
|
||||
version,
|
||||
public_key,
|
||||
hop_data,
|
||||
hmac,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Onion message payloads contain "control" TLVs and "data" TLVs. Control TLVs are used to route
|
||||
/// the onion message from hop to hop and for path verification, whereas data TLVs contain the onion
|
||||
/// message content itself, such as an invoice request.
|
||||
pub(super) enum Payload {
|
||||
/// This payload is for an intermediate hop.
|
||||
Forward(ForwardControlTlvs),
|
||||
/// This payload is for the final hop.
|
||||
Receive {
|
||||
control_tlvs: ReceiveControlTlvs,
|
||||
// Coming soon:
|
||||
// reply_path: Option<BlindedRoute>,
|
||||
// message: Message,
|
||||
}
|
||||
}
|
||||
|
||||
// Coming soon:
|
||||
// enum Message {
|
||||
// InvoiceRequest(InvoiceRequest),
|
||||
// Invoice(Invoice),
|
||||
// InvoiceError(InvoiceError),
|
||||
// CustomMessage<T>,
|
||||
// }
|
||||
|
||||
/// Forward control TLVs in their blinded and unblinded form.
|
||||
pub(super) enum ForwardControlTlvs {
|
||||
/// If we're sending to a blinded route, the node that constructed the blinded route has provided
|
||||
/// this hop's control TLVs, already encrypted into bytes.
|
||||
Blinded(Vec<u8>),
|
||||
/// If we're constructing an onion message hop through an intermediate unblinded node, we'll need
|
||||
/// to construct the intermediate hop's control TLVs in their unblinded state to avoid encoding
|
||||
/// them into an intermediate Vec. See [`super::blinded_route::ForwardTlvs`] for more info.
|
||||
Unblinded(ForwardTlvs),
|
||||
}
|
||||
|
||||
/// Receive control TLVs in their blinded and unblinded form.
|
||||
pub(super) enum ReceiveControlTlvs {
|
||||
/// See [`ForwardControlTlvs::Blinded`].
|
||||
Blinded(Vec<u8>),
|
||||
/// See [`ForwardControlTlvs::Unblinded`] and [`super::blinded_route::ReceiveTlvs`].
|
||||
Unblinded(ReceiveTlvs),
|
||||
}
|
||||
|
||||
// Uses the provided secret to simultaneously encode and encrypt the unblinded control TLVs.
|
||||
impl Writeable for (Payload, [u8; 32]) {
|
||||
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
|
||||
match &self.0 {
|
||||
Payload::Forward(ForwardControlTlvs::Blinded(encrypted_bytes)) |
|
||||
Payload::Receive { control_tlvs: ReceiveControlTlvs::Blinded(encrypted_bytes)} => {
|
||||
encode_varint_length_prefixed_tlv!(w, {
|
||||
(4, encrypted_bytes, vec_type)
|
||||
})
|
||||
},
|
||||
Payload::Forward(ForwardControlTlvs::Unblinded(control_tlvs)) => {
|
||||
let write_adapter = ChaChaPolyWriteAdapter::new(self.1, &control_tlvs);
|
||||
encode_varint_length_prefixed_tlv!(w, {
|
||||
(4, write_adapter, required)
|
||||
})
|
||||
},
|
||||
Payload::Receive { control_tlvs: ReceiveControlTlvs::Unblinded(control_tlvs)} => {
|
||||
let write_adapter = ChaChaPolyWriteAdapter::new(self.1, &control_tlvs);
|
||||
encode_varint_length_prefixed_tlv!(w, {
|
||||
(4, write_adapter, required)
|
||||
})
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Uses the provided secret to simultaneously decode and decrypt the control TLVs.
|
||||
impl ReadableArgs<SharedSecret> for Payload {
|
||||
fn read<R: Read>(mut r: &mut R, encrypted_tlvs_ss: SharedSecret) -> Result<Self, DecodeError> {
|
||||
use bitcoin::consensus::encode::{Decodable, Error, VarInt};
|
||||
let v: VarInt = Decodable::consensus_decode(&mut r)
|
||||
.map_err(|e| match e {
|
||||
Error::Io(ioe) => DecodeError::from(ioe),
|
||||
_ => DecodeError::InvalidValue
|
||||
})?;
|
||||
|
||||
let mut rd = FixedLengthReader::new(r, v.0);
|
||||
// TODO: support reply paths
|
||||
let mut _reply_path_bytes: Option<Vec<u8>> = Some(Vec::new());
|
||||
let mut read_adapter: Option<ChaChaPolyReadAdapter<ControlTlvs>> = None;
|
||||
let rho = onion_utils::gen_rho_from_shared_secret(&encrypted_tlvs_ss.secret_bytes());
|
||||
decode_tlv_stream!(&mut rd, {
|
||||
(2, _reply_path_bytes, vec_type),
|
||||
(4, read_adapter, (option: LengthReadableArgs, rho))
|
||||
});
|
||||
rd.eat_remaining().map_err(|_| DecodeError::ShortRead)?;
|
||||
|
||||
match read_adapter {
|
||||
None => return Err(DecodeError::InvalidValue),
|
||||
Some(ChaChaPolyReadAdapter { readable: ControlTlvs::Forward(tlvs)}) => {
|
||||
Ok(Payload::Forward(ForwardControlTlvs::Unblinded(tlvs)))
|
||||
},
|
||||
Some(ChaChaPolyReadAdapter { readable: ControlTlvs::Receive(tlvs)}) => {
|
||||
Ok(Payload::Receive { control_tlvs: ReceiveControlTlvs::Unblinded(tlvs)})
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// When reading a packet off the wire, we don't know a priori whether the packet is to be forwarded
|
||||
/// or received. Thus we read a ControlTlvs rather than reading a ForwardControlTlvs or
|
||||
/// ReceiveControlTlvs directly.
|
||||
pub(super) enum ControlTlvs {
|
||||
/// This onion message is intended to be forwarded.
|
||||
Forward(ForwardTlvs),
|
||||
/// This onion message is intended to be received.
|
||||
Receive(ReceiveTlvs),
|
||||
}
|
||||
|
||||
impl Readable for ControlTlvs {
|
||||
fn read<R: Read>(mut r: &mut R) -> Result<Self, DecodeError> {
|
||||
let mut _padding: Option<Padding> = None;
|
||||
let mut _short_channel_id: Option<u64> = None;
|
||||
let mut next_node_id: Option<PublicKey> = None;
|
||||
let mut path_id: Option<[u8; 32]> = None;
|
||||
let mut next_blinding_override: Option<PublicKey> = None;
|
||||
decode_tlv_stream!(&mut r, {
|
||||
(1, _padding, option),
|
||||
(2, _short_channel_id, option),
|
||||
(4, next_node_id, option),
|
||||
(6, path_id, option),
|
||||
(8, next_blinding_override, option),
|
||||
});
|
||||
|
||||
let valid_fwd_fmt = next_node_id.is_some() && path_id.is_none();
|
||||
let valid_recv_fmt = next_node_id.is_none() && next_blinding_override.is_none();
|
||||
|
||||
let payload_fmt = if valid_fwd_fmt {
|
||||
ControlTlvs::Forward(ForwardTlvs {
|
||||
next_node_id: next_node_id.unwrap(),
|
||||
next_blinding_override,
|
||||
})
|
||||
} else if valid_recv_fmt {
|
||||
ControlTlvs::Receive(ReceiveTlvs {
|
||||
path_id,
|
||||
})
|
||||
} else {
|
||||
return Err(DecodeError::InvalidValue)
|
||||
};
|
||||
|
||||
Ok(payload_fmt)
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads padding to the end, ignoring what's read.
|
||||
pub(crate) struct Padding {}
|
||||
impl Readable for Padding {
|
||||
#[inline]
|
||||
fn read<R: Read>(reader: &mut R) -> Result<Self, DecodeError> {
|
||||
loop {
|
||||
let mut buf = [0; 8192];
|
||||
if reader.read(&mut buf[..])? == 0 { break; }
|
||||
}
|
||||
Ok(Self {})
|
||||
}
|
||||
}
|
100
lightning/src/onion_message/utils.rs
Normal file
100
lightning/src/onion_message/utils.rs
Normal file
|
@ -0,0 +1,100 @@
|
|||
// This file is Copyright its original authors, visible in version control
|
||||
// history.
|
||||
//
|
||||
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
|
||||
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
|
||||
// You may not use this file except in accordance with one or both of these
|
||||
// licenses.
|
||||
|
||||
//! Onion message utility methods live here.
|
||||
|
||||
use bitcoin::hashes::{Hash, HashEngine};
|
||||
use bitcoin::hashes::hmac::{Hmac, HmacEngine};
|
||||
use bitcoin::hashes::sha256::Hash as Sha256;
|
||||
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
|
||||
use bitcoin::secp256k1::ecdh::SharedSecret;
|
||||
|
||||
use ln::onion_utils;
|
||||
use super::blinded_route::BlindedRoute;
|
||||
use super::messenger::Destination;
|
||||
|
||||
use prelude::*;
|
||||
|
||||
// TODO: DRY with onion_utils::construct_onion_keys_callback
|
||||
#[inline]
|
||||
pub(super) fn construct_keys_callback<T: secp256k1::Signing + secp256k1::Verification,
|
||||
FType: FnMut(PublicKey, SharedSecret, PublicKey, [u8; 32], Option<PublicKey>, Option<Vec<u8>>)>(
|
||||
secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], destination: Option<Destination>,
|
||||
session_priv: &SecretKey, mut callback: FType
|
||||
) -> Result<(), secp256k1::Error> {
|
||||
let mut msg_blinding_point_priv = session_priv.clone();
|
||||
let mut msg_blinding_point = PublicKey::from_secret_key(secp_ctx, &msg_blinding_point_priv);
|
||||
let mut onion_packet_pubkey_priv = msg_blinding_point_priv.clone();
|
||||
let mut onion_packet_pubkey = msg_blinding_point.clone();
|
||||
|
||||
macro_rules! build_keys {
|
||||
($pk: expr, $blinded: expr, $encrypted_payload: expr) => {{
|
||||
let encrypted_data_ss = SharedSecret::new(&$pk, &msg_blinding_point_priv);
|
||||
|
||||
let blinded_hop_pk = if $blinded { $pk } else {
|
||||
let hop_pk_blinding_factor = {
|
||||
let mut hmac = HmacEngine::<Sha256>::new(b"blinded_node_id");
|
||||
hmac.input(encrypted_data_ss.as_ref());
|
||||
Hmac::from_engine(hmac).into_inner()
|
||||
};
|
||||
let mut unblinded_pk = $pk;
|
||||
unblinded_pk.mul_assign(secp_ctx, &hop_pk_blinding_factor)?;
|
||||
unblinded_pk
|
||||
};
|
||||
let onion_packet_ss = SharedSecret::new(&blinded_hop_pk, &onion_packet_pubkey_priv);
|
||||
|
||||
let rho = onion_utils::gen_rho_from_shared_secret(encrypted_data_ss.as_ref());
|
||||
let unblinded_pk_opt = if $blinded { None } else { Some($pk) };
|
||||
callback(blinded_hop_pk, onion_packet_ss, onion_packet_pubkey, rho, unblinded_pk_opt, $encrypted_payload);
|
||||
(encrypted_data_ss, onion_packet_ss)
|
||||
}}
|
||||
}
|
||||
|
||||
macro_rules! build_keys_in_loop {
|
||||
($pk: expr, $blinded: expr, $encrypted_payload: expr) => {
|
||||
let (encrypted_data_ss, onion_packet_ss) = build_keys!($pk, $blinded, $encrypted_payload);
|
||||
|
||||
let msg_blinding_point_blinding_factor = {
|
||||
let mut sha = Sha256::engine();
|
||||
sha.input(&msg_blinding_point.serialize()[..]);
|
||||
sha.input(encrypted_data_ss.as_ref());
|
||||
Sha256::from_engine(sha).into_inner()
|
||||
};
|
||||
|
||||
msg_blinding_point_priv.mul_assign(&msg_blinding_point_blinding_factor)?;
|
||||
msg_blinding_point = PublicKey::from_secret_key(secp_ctx, &msg_blinding_point_priv);
|
||||
|
||||
let onion_packet_pubkey_blinding_factor = {
|
||||
let mut sha = Sha256::engine();
|
||||
sha.input(&onion_packet_pubkey.serialize()[..]);
|
||||
sha.input(onion_packet_ss.as_ref());
|
||||
Sha256::from_engine(sha).into_inner()
|
||||
};
|
||||
onion_packet_pubkey_priv.mul_assign(&onion_packet_pubkey_blinding_factor)?;
|
||||
onion_packet_pubkey = PublicKey::from_secret_key(secp_ctx, &onion_packet_pubkey_priv);
|
||||
};
|
||||
}
|
||||
|
||||
for pk in unblinded_path {
|
||||
build_keys_in_loop!(*pk, false, None);
|
||||
}
|
||||
if let Some(dest) = destination {
|
||||
match dest {
|
||||
Destination::Node(pk) => {
|
||||
build_keys!(pk, false, None);
|
||||
},
|
||||
Destination::BlindedRoute(BlindedRoute { blinded_hops, .. }) => {
|
||||
for hop in blinded_hops {
|
||||
build_keys_in_loop!(hop.blinded_node_id, true, Some(hop.encrypted_payload));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
|
@ -244,6 +244,14 @@ pub(crate) trait LengthReadableArgs<P> where Self: Sized
|
|||
fn read<R: LengthRead>(reader: &mut R, params: P) -> Result<Self, DecodeError>;
|
||||
}
|
||||
|
||||
/// A trait that various higher-level rust-lightning types implement allowing them to be read in
|
||||
/// from a Read, requiring the implementer to provide the total length of the read.
|
||||
pub(crate) trait LengthReadable where Self: Sized
|
||||
{
|
||||
/// Reads a Self in from the given LengthRead
|
||||
fn read<R: LengthRead>(reader: &mut R) -> Result<Self, DecodeError>;
|
||||
}
|
||||
|
||||
/// A trait that various rust-lightning types implement allowing them to (maybe) be read in from a Read
|
||||
///
|
||||
/// (C-not exported) as we only export serialization to/from byte arrays instead
|
||||
|
|
|
@ -35,6 +35,7 @@ use bitcoin::network::constants::Network;
|
|||
use bitcoin::hash_types::{BlockHash, Txid};
|
||||
|
||||
use bitcoin::secp256k1::{SecretKey, PublicKey, Secp256k1, ecdsa::Signature};
|
||||
use bitcoin::secp256k1::ecdh::SharedSecret;
|
||||
use bitcoin::secp256k1::ecdsa::RecoverableSignature;
|
||||
|
||||
use regex;
|
||||
|
@ -73,6 +74,7 @@ impl keysinterface::KeysInterface for OnlyReadsKeysInterface {
|
|||
type Signer = EnforcingSigner;
|
||||
|
||||
fn get_node_secret(&self, _recipient: Recipient) -> Result<SecretKey, ()> { unreachable!(); }
|
||||
fn ecdh(&self, _recipient: Recipient, _other_key: &PublicKey, _tweak: Option<&[u8; 32]>) -> Result<SharedSecret, ()> { unreachable!(); }
|
||||
fn get_inbound_payment_key_material(&self) -> KeyMaterial { unreachable!(); }
|
||||
fn get_destination_script(&self) -> Script { unreachable!(); }
|
||||
fn get_shutdown_scriptpubkey(&self) -> ShutdownScript { unreachable!(); }
|
||||
|
@ -598,6 +600,9 @@ impl keysinterface::KeysInterface for TestKeysInterface {
|
|||
fn get_node_secret(&self, recipient: Recipient) -> Result<SecretKey, ()> {
|
||||
self.backing.get_node_secret(recipient)
|
||||
}
|
||||
fn ecdh(&self, recipient: Recipient, other_key: &PublicKey, tweak: Option<&[u8; 32]>) -> Result<SharedSecret, ()> {
|
||||
self.backing.ecdh(recipient, other_key, tweak)
|
||||
}
|
||||
fn get_inbound_payment_key_material(&self) -> keysinterface::KeyMaterial {
|
||||
self.backing.get_inbound_payment_key_material()
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue