From 803d6b6e2fb8664a671c92156e6b1fb11aba0ab1 Mon Sep 17 00:00:00 2001 From: Devrandom Date: Fri, 21 Jan 2022 11:33:39 +0100 Subject: [PATCH] Improve KeysInterface::sign_invoice API split hrp from invoice data, to allow parsing, since there is no delimiter between the two parts --- fuzz/src/chanmon_consistency.rs | 3 ++- fuzz/src/full_stack.rs | 3 ++- lightning-invoice/src/lib.rs | 26 ++------------------------ lightning-invoice/src/utils.rs | 5 ++--- lightning/src/chain/keysinterface.rs | 14 +++++++++----- lightning/src/ln/channel.rs | 3 ++- lightning/src/util/invoice.rs | 26 ++++++++++++++++++++++++++ lightning/src/util/mod.rs | 1 + lightning/src/util/test_utils.rs | 7 ++++--- 9 files changed, 50 insertions(+), 38 deletions(-) create mode 100644 lightning/src/util/invoice.rs diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index f41b17a1..7ac628d4 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -63,6 +63,7 @@ use std::collections::{HashSet, hash_map, HashMap}; use std::sync::{Arc,Mutex}; use std::sync::atomic; use std::io::Cursor; +use bitcoin::bech32::u5; const MAX_FEE: u32 = 10_000; struct FuzzEstimator { @@ -220,7 +221,7 @@ impl KeysInterface for KeyProvider { }) } - fn sign_invoice(&self, _invoice_preimage: Vec) -> Result { + fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5]) -> Result { unreachable!() } } diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 34c6f554..c238aca6 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -60,6 +60,7 @@ use std::convert::TryInto; use std::cmp; use std::sync::{Arc, Mutex}; use std::sync::atomic::{AtomicU64,AtomicUsize,Ordering}; +use bitcoin::bech32::u5; #[inline] pub fn slice_to_be16(v: &[u8]) -> u16 { @@ -333,7 +334,7 @@ impl KeysInterface for KeyProvider { )) } - fn sign_invoice(&self, _invoice_preimage: Vec) -> Result { + fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5]) -> Result { unreachable!() } } diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index b6abecf3..681ea3d5 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -43,6 +43,7 @@ use lightning::ln::features::InvoiceFeatures; #[cfg(any(doc, test))] use lightning::routing::network_graph::RoutingFees; use lightning::routing::router::RouteHint; +use lightning::util::invoice::construct_invoice_preimage; use secp256k1::key::PublicKey; use secp256k1::{Message, Secp256k1}; @@ -869,32 +870,9 @@ macro_rules! find_all_extract { #[allow(missing_docs)] impl RawInvoice { - /// Construct the invoice's HRP and signatureless data into a preimage to be hashed. - pub(crate) fn construct_invoice_preimage(hrp_bytes: &[u8], data_without_signature: &[u5]) -> Vec { - use bech32::FromBase32; - - let mut preimage = Vec::::from(hrp_bytes); - - let mut data_part = Vec::from(data_without_signature); - let overhang = (data_part.len() * 5) % 8; - if overhang > 0 { - // add padding if data does not end at a byte boundary - data_part.push(u5::try_from_u8(0).unwrap()); - - // if overhang is in (1..3) we need to add u5(0) padding two times - if overhang < 3 { - data_part.push(u5::try_from_u8(0).unwrap()); - } - } - - preimage.extend_from_slice(&Vec::::from_base32(&data_part) - .expect("No padding error may occur due to appended zero above.")); - preimage - } - /// Hash the HRP as bytes and signatureless data part. fn hash_from_parts(hrp_bytes: &[u8], data_without_signature: &[u5]) -> [u8; 32] { - let preimage = RawInvoice::construct_invoice_preimage(hrp_bytes, data_without_signature); + let preimage = construct_invoice_preimage(hrp_bytes, data_without_signature); let mut hash: [u8; 32] = Default::default(); hash.copy_from_slice(&sha256::Hash::hash(&preimage)[..]); hash diff --git a/lightning-invoice/src/utils.rs b/lightning-invoice/src/utils.rs index ffa84c98..189da28d 100644 --- a/lightning-invoice/src/utils.rs +++ b/lightning-invoice/src/utils.rs @@ -1,6 +1,6 @@ //! Convenient utilities to create an invoice. -use {CreationError, Currency, DEFAULT_EXPIRY_TIME, Invoice, InvoiceBuilder, SignOrCreationError, RawInvoice}; +use {CreationError, Currency, DEFAULT_EXPIRY_TIME, Invoice, InvoiceBuilder, SignOrCreationError}; use payment::{Payer, Router}; use bech32::ToBase32; @@ -118,8 +118,7 @@ where let hrp_str = raw_invoice.hrp.to_string(); let hrp_bytes = hrp_str.as_bytes(); let data_without_signature = raw_invoice.data.to_base32(); - let invoice_preimage = RawInvoice::construct_invoice_preimage(hrp_bytes, &data_without_signature); - let signed_raw_invoice = raw_invoice.sign(|_| keys_manager.sign_invoice(invoice_preimage)); + let signed_raw_invoice = raw_invoice.sign(|_| keys_manager.sign_invoice(hrp_bytes, &data_without_signature)); match signed_raw_invoice { Ok(inv) => Ok(Invoice::from_signed(inv).unwrap()), Err(e) => Err(SignOrCreationError::SignError(e)) diff --git a/lightning/src/chain/keysinterface.rs b/lightning/src/chain/keysinterface.rs index a7965d4f..c44c3e82 100644 --- a/lightning/src/chain/keysinterface.rs +++ b/lightning/src/chain/keysinterface.rs @@ -18,6 +18,7 @@ use bitcoin::network::constants::Network; use bitcoin::util::bip32::{ExtendedPrivKey, ExtendedPubKey, ChildNumber}; use bitcoin::util::bip143; +use bitcoin::bech32::u5; use bitcoin::hashes::{Hash, HashEngine}; use bitcoin::hashes::sha256::HashEngine as Sha256State; use bitcoin::hashes::sha256::Hash as Sha256; @@ -42,6 +43,7 @@ use prelude::*; use core::sync::atomic::{AtomicUsize, Ordering}; use io::{self, Error}; use ln::msgs::{DecodeError, MAX_VALUE_MSAT}; +use util::invoice::construct_invoice_preimage; /// Used as initial key material, to be expanded into multiple secret keys (but not to be used /// directly). This is used within LDK to encrypt/decrypt inbound payment data. @@ -398,11 +400,12 @@ pub trait KeysInterface { /// you've read all of the provided bytes to ensure no corruption occurred. fn read_chan_signer(&self, reader: &[u8]) -> Result; - /// Sign an invoice's preimage (note that this is the preimage of the invoice, not the HTLC's - /// preimage). By parameterizing by the preimage instead of the hash, we allow implementors of + /// Sign an invoice. + /// By parameterizing by the raw invoice bytes instead of the hash, we allow implementors of /// this trait to parse the invoice and make sure they're signing what they expect, rather than /// blindly signing the hash. - fn sign_invoice(&self, invoice_preimage: Vec) -> Result; + /// The hrp is ascii bytes, while the invoice data is base32. + fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5]) -> Result; /// Get secret key material as bytes for use in encrypting and decrypting inbound payment data. /// @@ -1092,8 +1095,9 @@ impl KeysInterface for KeysManager { InMemorySigner::read(&mut io::Cursor::new(reader)) } - fn sign_invoice(&self, invoice_preimage: Vec) -> Result { - Ok(self.secp_ctx.sign_recoverable(&hash_to_message!(&Sha256::hash(&invoice_preimage)), &self.get_node_secret())) + fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5]) -> Result { + let preimage = construct_invoice_preimage(&hrp_bytes, &invoice_data); + Ok(self.secp_ctx.sign_recoverable(&hash_to_message!(&Sha256::hash(&preimage)), &self.get_node_secret())) } } diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 03633cec..19f73ba6 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -5840,6 +5840,7 @@ mod tests { use bitcoin::hashes::Hash; use bitcoin::hash_types::{Txid, WPubkeyHash}; use core::num::NonZeroU8; + use bitcoin::bech32::u5; use sync::Arc; use prelude::*; @@ -5884,7 +5885,7 @@ mod tests { } fn get_secure_random_bytes(&self) -> [u8; 32] { [0; 32] } fn read_chan_signer(&self, _data: &[u8]) -> Result { panic!(); } - fn sign_invoice(&self, _invoice_preimage: Vec) -> Result { panic!(); } + fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5]) -> Result { panic!(); } } fn public_from_secret_hex(secp_ctx: &Secp256k1, hex: &str) -> PublicKey { diff --git a/lightning/src/util/invoice.rs b/lightning/src/util/invoice.rs new file mode 100644 index 00000000..0612ff73 --- /dev/null +++ b/lightning/src/util/invoice.rs @@ -0,0 +1,26 @@ +//! Low level invoice utilities. + +use bitcoin::bech32::{u5, FromBase32}; +use prelude::*; + +/// Construct the invoice's HRP and signatureless data into a preimage to be hashed. +pub fn construct_invoice_preimage(hrp_bytes: &[u8], data_without_signature: &[u5]) -> Vec { + let mut preimage = Vec::::from(hrp_bytes); + + let mut data_part = Vec::from(data_without_signature); + let overhang = (data_part.len() * 5) % 8; + if overhang > 0 { + // add padding if data does not end at a byte boundary + data_part.push(u5::try_from_u8(0).unwrap()); + + // if overhang is in (1..3) we need to add u5(0) padding two times + if overhang < 3 { + data_part.push(u5::try_from_u8(0).unwrap()); + } + } + + preimage.extend_from_slice(&Vec::::from_base32(&data_part) + .expect("No padding error may occur due to appended zero above.")); + preimage +} + diff --git a/lightning/src/util/mod.rs b/lightning/src/util/mod.rs index 34e66190..81b4bc92 100644 --- a/lightning/src/util/mod.rs +++ b/lightning/src/util/mod.rs @@ -19,6 +19,7 @@ pub mod events; pub mod errors; pub mod ser; pub mod message_signing; +pub mod invoice; pub(crate) mod atomic_counter; pub(crate) mod byte_utils; diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 5b3865ed..41695144 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -47,6 +47,7 @@ use core::time::Duration; use sync::{Mutex, Arc}; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use core::{cmp, mem}; +use bitcoin::bech32::u5; use chain::keysinterface::{InMemorySigner, KeyMaterial}; pub struct TestVecWriter(pub Vec); @@ -87,7 +88,7 @@ impl keysinterface::KeysInterface for OnlyReadsKeysInterface { false )) } - fn sign_invoice(&self, _invoice_preimage: Vec) -> Result { unreachable!(); } + fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5]) -> Result { unreachable!(); } } pub struct TestChainMonitor<'a> { @@ -528,8 +529,8 @@ impl keysinterface::KeysInterface for TestKeysInterface { )) } - fn sign_invoice(&self, invoice_preimage: Vec) -> Result { - self.backing.sign_invoice(invoice_preimage) + fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5]) -> Result { + self.backing.sign_invoice(hrp_bytes, invoice_data) } } -- 2.30.2