From 59b1623d4ba5c5719937591c407905fb108ec4c4 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 30 Jan 2026 11:02:24 -0600 Subject: [PATCH] invoice: Use PaymentHash in raw invoice types --- lightning-invoice/src/de.rs | 141 ++++++++----- lightning-invoice/src/lib.rs | 141 +++++++------ lightning-invoice/src/ser.rs | 18 +- lightning-invoice/src/test_ser_de.rs | 29 +-- lightning-invoice/tests/ser_de.rs | 192 +++++++++++++----- .../tests/lsps2_integration_tests.rs | 2 +- lightning/src/ln/bolt11_payment_tests.rs | 6 +- lightning/src/ln/channelmanager.rs | 5 +- lightning/src/ln/invoice_utils.rs | 8 +- 9 files changed, 355 insertions(+), 187 deletions(-) diff --git a/lightning-invoice/src/de.rs b/lightning-invoice/src/de.rs index 0747015a457..f1bbe29440a 100644 --- a/lightning-invoice/src/de.rs +++ b/lightning-invoice/src/de.rs @@ -16,7 +16,7 @@ use crate::Bolt11Bech32; use bitcoin::hashes::sha256; use bitcoin::hashes::Hash; use bitcoin::{PubkeyHash, ScriptHash, WitnessVersion}; -use lightning_types::payment::PaymentSecret; +use lightning_types::payment::{PaymentHash, PaymentSecret}; use lightning_types::routing::{RouteHint, RouteHintHop, RoutingFees}; use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId}; @@ -89,6 +89,18 @@ impl FromBase32 for PaymentSecret { } } +impl FromBase32 for PaymentHash { + type Err = Bolt11ParseError; + + fn from_base32(field_data: &[Fe32]) -> Result { + if field_data.len() != 52 { + return Err(Bolt11ParseError::InvalidSliceLength(field_data.len(), 52, "PaymentHash")); + } + let data_bytes = <[u8; 32]>::from_base32(field_data)?; + Ok(PaymentHash(data_bytes)) + } +} + impl FromBase32 for Bolt11InvoiceFeatures { type Err = Bolt11ParseError; @@ -540,7 +552,7 @@ impl FromBase32 for TaggedField { match tag.to_u8() { constants::TAG_PAYMENT_HASH => { - Ok(TaggedField::PaymentHash(Sha256::from_base32(field_data)?)) + Ok(TaggedField::PaymentHash(PaymentHash::from_base32(field_data)?)) }, constants::TAG_DESCRIPTION => { Ok(TaggedField::Description(Description::from_base32(field_data)?)) @@ -1068,8 +1080,9 @@ mod test { use crate::TaggedField::*; use crate::{ Bolt11InvoiceSignature, Currency, PositiveTimestamp, RawBolt11Invoice, RawDataPart, - RawHrp, Sha256, SiPrefix, SignedRawBolt11Invoice, + RawHrp, SiPrefix, SignedRawBolt11Invoice, }; + use bitcoin::hex::FromHex; use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId}; use lightning_types::features::Bolt11InvoiceFeatures; @@ -1077,45 +1090,51 @@ mod test { let expected_features = Bolt11InvoiceFeatures::from_le_bytes(vec![0, 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8]); let invoice_str = "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q5sqqqqqqqqqqqqqqqpqsq67gye39hfg3zd8rgc80k32tvy9xk2xunwm5lzexnvpx6fd77en8qaq424dxgt56cag2dpt359k3ssyhetktkpqh24jqnjyw6uqd08sgptq44qu"; - let invoice = - SignedRawBolt11Invoice { - raw_invoice: RawBolt11Invoice { - hrp: RawHrp { - currency: Currency::Bitcoin, - raw_amount: Some(25), - si_prefix: Some(SiPrefix::Milli), - }, - data: RawDataPart { - timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(), - tagged_fields: vec ! [ - PaymentHash(Sha256(sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102" - ).unwrap())).into(), + let invoice = SignedRawBolt11Invoice { + raw_invoice: RawBolt11Invoice { + hrp: RawHrp { + currency: Currency::Bitcoin, + raw_amount: Some(25), + si_prefix: Some(SiPrefix::Milli), + }, + data: RawDataPart { + timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(), + tagged_fields: vec ! [ + crate::TaggedField::PaymentHash(crate::PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102", + ) + .unwrap(), + ) + .unwrap(), + )) + .into(), Description(crate::Description::new("coffee beans".to_owned()).unwrap()).into(), PaymentSecret(crate::PaymentSecret([17; 32])).into(), Features(expected_features).into()], - }, }, - hash: [ - 0xb1, 0x96, 0x46, 0xc3, 0xbc, 0x56, 0x76, 0x1d, 0x20, 0x65, 0x6e, 0x0e, 0x32, - 0xec, 0xd2, 0x69, 0x27, 0xb7, 0x62, 0x6e, 0x2a, 0x8b, 0xe6, 0x97, 0x71, 0x9f, - 0xf8, 0x7e, 0x44, 0x54, 0x55, 0xb9, - ], - signature: Bolt11InvoiceSignature( - RecoverableSignature::from_compact( - &[ - 0xd7, 0x90, 0x4c, 0xc4, 0xb7, 0x4a, 0x22, 0x26, 0x9c, 0x68, 0xc1, 0xdf, - 0x68, 0xa9, 0x6c, 0x21, 0x4d, 0x65, 0x1b, 0x93, 0x76, 0xe9, 0xf1, 0x64, - 0xd3, 0x60, 0x4d, 0xa4, 0xb7, 0xde, 0xcc, 0xce, 0x0e, 0x82, 0xaa, 0xab, - 0x4c, 0x85, 0xd3, 0x58, 0xea, 0x14, 0xd0, 0xae, 0x34, 0x2d, 0xa3, 0x08, - 0x12, 0xf9, 0x5d, 0x97, 0x60, 0x82, 0xea, 0xac, 0x81, 0x39, 0x11, 0xda, - 0xe0, 0x1a, 0xf3, 0xc1, - ], - RecoveryId::from_i32(1).unwrap(), - ) - .unwrap(), - ), - }; + }, + hash: [ + 0xb1, 0x96, 0x46, 0xc3, 0xbc, 0x56, 0x76, 0x1d, 0x20, 0x65, 0x6e, 0x0e, 0x32, 0xec, + 0xd2, 0x69, 0x27, 0xb7, 0x62, 0x6e, 0x2a, 0x8b, 0xe6, 0x97, 0x71, 0x9f, 0xf8, 0x7e, + 0x44, 0x54, 0x55, 0xb9, + ], + signature: Bolt11InvoiceSignature( + RecoverableSignature::from_compact( + &[ + 0xd7, 0x90, 0x4c, 0xc4, 0xb7, 0x4a, 0x22, 0x26, 0x9c, 0x68, 0xc1, 0xdf, + 0x68, 0xa9, 0x6c, 0x21, 0x4d, 0x65, 0x1b, 0x93, 0x76, 0xe9, 0xf1, 0x64, + 0xd3, 0x60, 0x4d, 0xa4, 0xb7, 0xde, 0xcc, 0xce, 0x0e, 0x82, 0xaa, 0xab, + 0x4c, 0x85, 0xd3, 0x58, 0xea, 0x14, 0xd0, 0xae, 0x34, 0x2d, 0xa3, 0x08, + 0x12, 0xf9, 0x5d, 0x97, 0x60, 0x82, 0xea, 0xac, 0x81, 0x39, 0x11, 0xda, + 0xe0, 0x1a, 0xf3, 0xc1, + ], + RecoveryId::from_i32(1).unwrap(), + ) + .unwrap(), + ), + }; assert_eq!(invoice_str, invoice.to_string()); assert_eq!(invoice_str.parse(), Ok(invoice)); } @@ -1125,8 +1144,9 @@ mod test { use crate::TaggedField::*; use crate::{ Bolt11InvoiceSignature, Currency, PositiveTimestamp, RawBolt11Invoice, RawDataPart, - RawHrp, Sha256, SignedRawBolt11Invoice, + RawHrp, SignedRawBolt11Invoice, }; + use bitcoin::hex::FromHex; use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId}; assert_eq!( @@ -1143,9 +1163,16 @@ mod test { data: RawDataPart { timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(), tagged_fields: vec ! [ - PaymentHash(Sha256(sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102" - ).unwrap())).into(), + crate::TaggedField::PaymentHash(crate::PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102", + ) + .unwrap(), + ) + .unwrap(), + )) + .into(), Description( crate::Description::new( "Please consider supporting this project".to_owned() @@ -1289,9 +1316,10 @@ mod test { use crate::TaggedField::*; use crate::{ Bolt11Invoice, Bolt11InvoiceFeatures, Bolt11InvoiceSignature, Currency, - PositiveTimestamp, RawBolt11Invoice, RawDataPart, RawHrp, RawTaggedField, Sha256, + PositiveTimestamp, RawBolt11Invoice, RawDataPart, RawHrp, RawTaggedField, SignedRawBolt11Invoice, }; + use bitcoin::hex::FromHex; use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId}; use bitcoin::secp256k1::PublicKey; use lightning_types::routing::{RouteHint, RouteHintHop, RoutingFees}; @@ -1310,10 +1338,13 @@ mod test { } // Invoice fields - let payment_hash = sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102", - ) - .unwrap(); + let payment_hash = crate::PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex("0001020304050607080900010203040506070809000102030405060708090102") + .unwrap(), + ) + .unwrap(), + ); let description = "A".repeat(639); let fallback_addr = crate::Fallback::SegWitProgram { version: bitcoin::WitnessVersion::V0, @@ -1346,7 +1377,7 @@ mod test { data: RawDataPart { timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(), tagged_fields: vec![ - PaymentHash(Sha256(payment_hash)).into(), + crate::TaggedField::PaymentHash(payment_hash).into(), Description(crate::Description::new(description).unwrap()).into(), PayeePubKey(crate::PayeePubKey(payee_pk)).into(), ExpiryTime(crate::ExpiryTime(std::time::Duration::from_secs(u64::MAX))).into(), @@ -1414,4 +1445,18 @@ mod test { assert!(parse_is_code_length_err(&too_long)); assert!(!parse_is_code_length_err(&too_long[..too_long.len() - 1])); } + + #[test] + fn test_payment_hash_from_base32_invalid_len() { + use crate::PaymentHash; + + // PaymentHash must be 52 base32 characters (32 bytes). + // Test with 51 characters (too short). + let input = vec![Fe32::try_from(0).unwrap(); 51]; + assert!(PaymentHash::from_base32(&input).is_err()); + + // Test with 53 characters (too long). + let input = vec![Fe32::try_from(0).unwrap(); 53]; + assert!(PaymentHash::from_base32(&input).is_err()); + } } diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index a83130ab799..4ee9acb5f27 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -189,7 +189,7 @@ impl Checksum for Bolt11Bech32 { /// /// use lightning_types::payment::PaymentSecret; /// -/// use lightning_invoice::{Currency, InvoiceBuilder}; +/// use lightning_invoice::{Currency, InvoiceBuilder, PaymentHash}; /// /// # #[cfg(not(feature = "std"))] /// # fn main() {} @@ -203,7 +203,7 @@ impl Checksum for Bolt11Bech32 { /// ][..] /// ).unwrap(); /// -/// let payment_hash = sha256::Hash::from_slice(&[0; 32][..]).unwrap(); +/// let payment_hash = PaymentHash([0; 32]); /// let payment_secret = PaymentSecret([42u8; 32]); /// /// let invoice = InvoiceBuilder::new(Currency::Bitcoin) @@ -521,7 +521,7 @@ impl Ord for RawTaggedField { #[allow(missing_docs)] #[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] pub enum TaggedField { - PaymentHash(Sha256), + PaymentHash(PaymentHash), Description(Description), PayeePubKey(PayeePubKey), DescriptionHash(Sha256), @@ -793,8 +793,8 @@ impl InvoiceBuilder { /// Set the payment hash. This function is only available if no payment hash was set. - pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder { - self.tagged_fields.push(TaggedField::PaymentHash(Sha256(hash))); + pub fn payment_hash(mut self, hash: PaymentHash) -> InvoiceBuilder { + self.tagged_fields.push(TaggedField::PaymentHash(hash)); self.set_flags() } } @@ -1158,7 +1158,7 @@ impl RawBolt11Invoice { self.data.tagged_fields.iter().filter_map(match_raw) } - pub fn payment_hash(&self) -> Option<&Sha256> { + pub fn payment_hash(&self) -> Option<&PaymentHash> { find_extract!(self.known_tagged_fields(), TaggedField::PaymentHash(ref x), x) } @@ -1461,8 +1461,7 @@ impl Bolt11Invoice { /// Returns the hash to which we will receive the preimage on completion of the payment pub fn payment_hash(&self) -> PaymentHash { - let hash = self.signed_invoice.payment_hash().expect("checked by constructor").0; - PaymentHash(hash.to_byte_array()) + *self.signed_invoice.payment_hash().expect("checked by constructor") } /// Return the description or a hash of it for longer ones @@ -1925,10 +1924,7 @@ impl<'de> Deserialize<'de> for Bolt11Invoice { #[cfg(test)] mod test { - use bitcoin::hashes::sha256; use bitcoin::ScriptBuf; - use std::str::FromStr; - #[test] fn test_system_time_bounds_assumptions() { assert_eq!( @@ -1940,16 +1936,22 @@ mod test { #[test] fn test_calc_invoice_hash() { use crate::TaggedField::*; - use crate::{Currency, PositiveTimestamp, RawBolt11Invoice, RawDataPart, RawHrp}; + use crate::{ + Currency, PaymentHash, PositiveTimestamp, RawBolt11Invoice, RawDataPart, RawHrp, + }; + use bitcoin::hex::FromHex; let invoice = RawBolt11Invoice { hrp: RawHrp { currency: Currency::Bitcoin, raw_amount: None, si_prefix: None }, data: RawDataPart { timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(), tagged_fields: vec![ - PaymentHash(crate::Sha256( - sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102", + crate::TaggedField::PaymentHash(PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102", + ) + .unwrap(), ) .unwrap(), )) @@ -1978,51 +1980,58 @@ mod test { fn test_check_signature() { use crate::TaggedField::*; use crate::{ - Bolt11InvoiceSignature, Currency, PositiveTimestamp, RawBolt11Invoice, RawDataPart, - RawHrp, Sha256, SignedRawBolt11Invoice, + Bolt11InvoiceSignature, Currency, PaymentHash, PositiveTimestamp, RawBolt11Invoice, + RawDataPart, RawHrp, SignedRawBolt11Invoice, }; + use bitcoin::hex::FromHex; use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId}; use bitcoin::secp256k1::Secp256k1; use bitcoin::secp256k1::{PublicKey, SecretKey}; - let invoice = - SignedRawBolt11Invoice { - raw_invoice: RawBolt11Invoice { - hrp: RawHrp { currency: Currency::Bitcoin, raw_amount: None, si_prefix: None }, - data: RawDataPart { - timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(), - tagged_fields: vec ! [ - PaymentHash(Sha256(sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102" - ).unwrap())).into(), + let invoice = SignedRawBolt11Invoice { + raw_invoice: RawBolt11Invoice { + hrp: RawHrp { currency: Currency::Bitcoin, raw_amount: None, si_prefix: None }, + data: RawDataPart { + timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(), + tagged_fields: vec ! [ + crate::TaggedField::PaymentHash(PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102", + ) + .unwrap(), + ) + .unwrap(), + )) + .into(), Description( crate::Description::new( "Please consider supporting this project".to_owned() ).unwrap() ).into(), ], - }, }, - hash: [ - 0xc3, 0xd4, 0xe8, 0x3f, 0x64, 0x6f, 0xa7, 0x9a, 0x39, 0x3d, 0x75, 0x27, 0x7b, - 0x1d, 0x85, 0x8d, 0xb1, 0xd1, 0xf7, 0xab, 0x71, 0x37, 0xdc, 0xb7, 0x83, 0x5d, - 0xb2, 0xec, 0xd5, 0x18, 0xe1, 0xc9, - ], - signature: Bolt11InvoiceSignature( - RecoverableSignature::from_compact( - &[ - 0x38u8, 0xec, 0x68, 0x91, 0x34, 0x5e, 0x20, 0x41, 0x45, 0xbe, 0x8a, - 0x3a, 0x99, 0xde, 0x38, 0xe9, 0x8a, 0x39, 0xd6, 0xa5, 0x69, 0x43, 0x4e, - 0x18, 0x45, 0xc8, 0xaf, 0x72, 0x05, 0xaf, 0xcf, 0xcc, 0x7f, 0x42, 0x5f, - 0xcd, 0x14, 0x63, 0xe9, 0x3c, 0x32, 0x88, 0x1e, 0xad, 0x0d, 0x6e, 0x35, - 0x6d, 0x46, 0x7e, 0xc8, 0xc0, 0x25, 0x53, 0xf9, 0xaa, 0xb1, 0x5e, 0x57, - 0x38, 0xb1, 0x1f, 0x12, 0x7f, - ], - RecoveryId::from_i32(0).unwrap(), - ) - .unwrap(), - ), - }; + }, + hash: [ + 0xc3, 0xd4, 0xe8, 0x3f, 0x64, 0x6f, 0xa7, 0x9a, 0x39, 0x3d, 0x75, 0x27, 0x7b, 0x1d, + 0x85, 0x8d, 0xb1, 0xd1, 0xf7, 0xab, 0x71, 0x37, 0xdc, 0xb7, 0x83, 0x5d, 0xb2, 0xec, + 0xd5, 0x18, 0xe1, 0xc9, + ], + signature: Bolt11InvoiceSignature( + RecoverableSignature::from_compact( + &[ + 0x38u8, 0xec, 0x68, 0x91, 0x34, 0x5e, 0x20, 0x41, 0x45, 0xbe, 0x8a, 0x3a, + 0x99, 0xde, 0x38, 0xe9, 0x8a, 0x39, 0xd6, 0xa5, 0x69, 0x43, 0x4e, 0x18, + 0x45, 0xc8, 0xaf, 0x72, 0x05, 0xaf, 0xcf, 0xcc, 0x7f, 0x42, 0x5f, 0xcd, + 0x14, 0x63, 0xe9, 0x3c, 0x32, 0x88, 0x1e, 0xad, 0x0d, 0x6e, 0x35, 0x6d, + 0x46, 0x7e, 0xc8, 0xc0, 0x25, 0x53, 0xf9, 0xaa, 0xb1, 0x5e, 0x57, 0x38, + 0xb1, 0x1f, 0x12, 0x7f, + ], + RecoveryId::from_i32(0).unwrap(), + ) + .unwrap(), + ), + }; assert!(invoice.check_signature()); @@ -2050,9 +2059,10 @@ mod test { fn test_check_feature_bits() { use crate::TaggedField::*; use crate::{ - Bolt11Invoice, Bolt11SemanticError, Currency, PositiveTimestamp, RawBolt11Invoice, - RawDataPart, RawHrp, Sha256, + Bolt11Invoice, Bolt11SemanticError, Currency, PaymentHash, PositiveTimestamp, + RawBolt11Invoice, RawDataPart, RawHrp, }; + use bitcoin::hex::FromHex; use bitcoin::secp256k1::Secp256k1; use bitcoin::secp256k1::SecretKey; use lightning_types::features::Bolt11InvoiceFeatures; @@ -2064,9 +2074,12 @@ mod test { data: RawDataPart { timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(), tagged_fields: vec![ - PaymentHash(Sha256( - sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102", + crate::TaggedField::PaymentHash(PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102", + ) + .unwrap(), ) .unwrap(), )) @@ -2174,7 +2187,7 @@ mod test { let builder = InvoiceBuilder::new(Currency::Bitcoin) .description("Test".into()) - .payment_hash(sha256::Hash::from_slice(&[0; 32][..]).unwrap()) + .payment_hash(PaymentHash([0; 32])) .duration_since_epoch(Duration::from_secs(1234567)); let invoice = builder.clone().amount_milli_satoshis(1500).build_raw().unwrap(); @@ -2196,7 +2209,7 @@ mod test { use std::iter::FromIterator; let builder = InvoiceBuilder::new(Currency::Bitcoin) - .payment_hash(sha256::Hash::from_slice(&[0; 32][..]).unwrap()) + .payment_hash(PaymentHash([0; 32])) .duration_since_epoch(Duration::from_secs(1234567)) .min_final_cltv_expiry_delta(144); @@ -2300,7 +2313,7 @@ mod test { .private_route(route_1.clone()) .private_route(route_2.clone()) .description_hash(sha256::Hash::from_slice(&[3; 32][..]).unwrap()) - .payment_hash(sha256::Hash::from_slice(&[21; 32][..]).unwrap()) + .payment_hash(PaymentHash([21; 32])) .payment_secret(PaymentSecret([42; 32])) .basic_mpp(); @@ -2361,7 +2374,7 @@ mod test { let signed_invoice = InvoiceBuilder::new(Currency::Bitcoin) .description("Test".into()) - .payment_hash(sha256::Hash::from_slice(&[0; 32][..]).unwrap()) + .payment_hash(PaymentHash([0; 32])) .payment_secret(PaymentSecret([0; 32])) .duration_since_epoch(Duration::from_secs(1234567)) .build_raw() @@ -2387,7 +2400,7 @@ mod test { let signed_invoice = InvoiceBuilder::new(Currency::Bitcoin) .description("Test".into()) - .payment_hash(sha256::Hash::from_slice(&[0; 32][..]).unwrap()) + .payment_hash(PaymentHash([0; 32])) .payment_secret(PaymentSecret([0; 32])) .duration_since_epoch(Duration::from_secs(1234567)) .build_raw() @@ -2428,13 +2441,13 @@ mod test { #[test] fn raw_tagged_field_ordering() { - use crate::{ - sha256, Description, Fe32, RawTaggedField, Sha256, TaggedField, UntrustedString, - }; + use crate::{Description, Fe32, PaymentHash, RawTaggedField, TaggedField, UntrustedString}; + use bitcoin::hex::FromHex; - let field10 = RawTaggedField::KnownSemantics(TaggedField::PaymentHash(Sha256( - sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102", + let field10 = RawTaggedField::KnownSemantics(crate::TaggedField::PaymentHash(PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex("0001020304050607080900010203040506070809000102030405060708090102") + .unwrap(), ) .unwrap(), ))); diff --git a/lightning-invoice/src/ser.rs b/lightning-invoice/src/ser.rs index 853accdd3ca..35d5dc024ea 100644 --- a/lightning-invoice/src/ser.rs +++ b/lightning-invoice/src/ser.rs @@ -7,9 +7,9 @@ use bech32::{ByteIterExt, Fe32, Fe32IterExt}; use super::{ constants, Bolt11Invoice, Bolt11InvoiceFeatures, Bolt11InvoiceSignature, Currency, Description, - ExpiryTime, Fallback, MinFinalCltvExpiryDelta, PayeePubKey, PaymentSecret, PositiveTimestamp, - PrivateRoute, RawDataPart, RawHrp, RawTaggedField, RouteHintHop, Sha256, SiPrefix, - SignedRawBolt11Invoice, TaggedField, + ExpiryTime, Fallback, MinFinalCltvExpiryDelta, PayeePubKey, PaymentHash, PaymentSecret, + PositiveTimestamp, PrivateRoute, RawDataPart, RawHrp, RawTaggedField, RouteHintHop, Sha256, + SiPrefix, SignedRawBolt11Invoice, TaggedField, }; macro_rules! define_iterator_enum { @@ -95,6 +95,18 @@ impl Base32Len for PaymentSecret { } } +impl Base32Iterable for PaymentHash { + fn fe_iter<'s>(&'s self) -> impl Iterator + 's { + self.0[..].fe_iter() + } +} + +impl Base32Len for PaymentHash { + fn base32_len(&self) -> usize { + 52 + } +} + impl Base32Iterable for Bolt11InvoiceFeatures { /// Convert to 5-bit values, by unpacking the 5 bit groups, /// putting the bytes from right-to-left, diff --git a/lightning-invoice/src/test_ser_de.rs b/lightning-invoice/src/test_ser_de.rs index e2e4d764ac2..efeed83980e 100644 --- a/lightning-invoice/src/test_ser_de.rs +++ b/lightning-invoice/src/test_ser_de.rs @@ -1,7 +1,11 @@ use crate::de::FromBase32; use crate::ser::{Base32Iterable, Base32Len}; -use crate::{sha256, PayeePubKey, PaymentSecret, PositiveTimestamp, RawDataPart, Sha256}; +use crate::{ + sha256, PayeePubKey, PaymentHash, PaymentSecret, PositiveTimestamp, RawDataPart, + RawTaggedField, Sha256, TaggedField, +}; use bech32::Fe32; +use bitcoin::hex::FromHex; use core::fmt::Debug; use std::str::FromStr; @@ -173,11 +177,12 @@ fn bolt11_invoice_features() { #[test] fn raw_tagged_field() { - use crate::TaggedField::PaymentHash; - - let field = PaymentHash(Sha256( - sha256::Hash::from_str("0001020304050607080900010203040506070809000102030405060708090102") - .unwrap(), + let field = TaggedField::PaymentHash(PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex("0001020304050607080900010203040506070809000102030405060708090102") + .unwrap(), + ) + .unwrap(), )); ser_de_test(field, "pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypq"); } @@ -202,17 +207,15 @@ fn description() { #[test] fn raw_data_part() { - use crate::TaggedField::PaymentHash; - let raw_data_part = RawDataPart { timestamp: PositiveTimestamp::from_unix_timestamp(10000).unwrap(), - tagged_fields: vec![PaymentHash(Sha256( - sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102", + tagged_fields: vec![RawTaggedField::KnownSemantics(TaggedField::PaymentHash(PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex("0001020304050607080900010203040506070809000102030405060708090102") + .unwrap(), ) .unwrap(), - )) - .into()], + )))], }; ser_de_test(raw_data_part, "qqqqfcspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypq"); } diff --git a/lightning-invoice/tests/ser_de.rs b/lightning-invoice/tests/ser_de.rs index b4d3fa758e1..353878a9c52 100644 --- a/lightning-invoice/tests/ser_de.rs +++ b/lightning-invoice/tests/ser_de.rs @@ -18,9 +18,15 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { InvoiceBuilder::new(Currency::Bitcoin) .duration_since_epoch(Duration::from_secs(1496314658)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102" - ).unwrap()) + .payment_hash(lightning_invoice::PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102", + ) + .unwrap(), + ) + .unwrap(), + )) .description("Please consider supporting this project".to_owned()) .build_raw() .unwrap() @@ -39,9 +45,15 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .amount_milli_satoshis(250_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102" - ).unwrap()) + .payment_hash(lightning_invoice::PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102", + ) + .unwrap(), + ) + .unwrap(), + )) .description("1 cup coffee".to_owned()) .expiry_time(Duration::from_secs(60)) .build_raw() @@ -61,9 +73,15 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .amount_milli_satoshis(250_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102" - ).unwrap()) + .payment_hash(lightning_invoice::PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102", + ) + .unwrap(), + ) + .unwrap(), + )) .description("ナンセンス 1杯".to_owned()) .expiry_time(Duration::from_secs(60)) .build_raw() @@ -84,9 +102,15 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .duration_since_epoch(Duration::from_secs(1496314658)) .description_hash(sha256::Hash::hash(b"One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102" - ).unwrap()) + .payment_hash(lightning_invoice::PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102", + ) + .unwrap(), + ) + .unwrap(), + )) .build_raw() .unwrap() .sign(|_| { @@ -105,9 +129,15 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .duration_since_epoch(Duration::from_secs(1496314658)) .description_hash(sha256::Hash::hash(b"One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102" - ).unwrap()) + .payment_hash(lightning_invoice::PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102", + ) + .unwrap(), + ) + .unwrap(), + )) .fallback(Fallback::PubKeyHash(PubkeyHash::from_slice(&[49, 114, 181, 101, 79, 102, 131, 200, 251, 20, 105, 89, 211, 71, 206, 48, 60, 174, 76, 167]).unwrap())) .build_raw() .unwrap() @@ -127,9 +157,15 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .duration_since_epoch(Duration::from_secs(1496314658)) .description_hash(sha256::Hash::hash(b"One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102" - ).unwrap()) + .payment_hash(lightning_invoice::PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102", + ) + .unwrap(), + ) + .unwrap(), + )) .fallback(Fallback::PubKeyHash(PubkeyHash::from_slice(&[4, 182, 31, 125, 193, 234, 13, 201, 148, 36, 70, 76, 196, 6, 77, 197, 100, 217, 30, 137]).unwrap())) .private_route(RouteHint(vec![RouteHintHop { src_node_id: PublicKey::from_slice(&>::from_hex( @@ -166,9 +202,15 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .duration_since_epoch(Duration::from_secs(1496314658)) .description_hash(sha256::Hash::hash(b"One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102" - ).unwrap()) + .payment_hash(lightning_invoice::PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102", + ) + .unwrap(), + ) + .unwrap(), + )) .fallback(Fallback::ScriptHash(ScriptHash::from_slice(&[143, 85, 86, 59, 154, 25, 243, 33, 194, 17, 233, 185, 243, 140, 223, 104, 110, 160, 120, 69]).unwrap())) .build_raw() .unwrap() @@ -188,9 +230,15 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .duration_since_epoch(Duration::from_secs(1496314658)) .description_hash(sha256::Hash::hash(b"One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102" - ).unwrap()) + .payment_hash(lightning_invoice::PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102", + ) + .unwrap(), + ) + .unwrap(), + )) .fallback(Fallback::SegWitProgram { version: WitnessVersion::V0, program: vec![117, 30, 118, 232, 25, 145, 150, 212, 84, 148, 28, 69, 209, 179, 163, 35, 241, 67, 59, 214] }) @@ -212,9 +260,15 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .duration_since_epoch(Duration::from_secs(1496314658)) .description_hash(sha256::Hash::hash(b"One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102" - ).unwrap()) + .payment_hash(lightning_invoice::PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102", + ) + .unwrap(), + ) + .unwrap(), + )) .fallback(Fallback::SegWitProgram { version: WitnessVersion::V0, program: vec![24, 99, 20, 60, 20, 197, 22, 104, 4, 189, 25, 32, 51, 86, 218, 19, 108, 152, 86, 120, 205, 77, 39, 161, 184, 198, 50, 150, 4, 144, 50, 98] }) @@ -235,9 +289,15 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .amount_milli_satoshis(967878534) .duration_since_epoch(Duration::from_secs(1572468703)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_str( - "462264ede7e14047e9b249da94fefc47f41f7d02ee9b091815a5506bc8abf75f" - ).unwrap()) + .payment_hash(lightning_invoice::PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "462264ede7e14047e9b249da94fefc47f41f7d02ee9b091815a5506bc8abf75f", + ) + .unwrap(), + ) + .unwrap(), + )) .expiry_time(Duration::from_secs(604800)) .min_final_cltv_expiry_delta(10) .description("Blockstream Store: 88.85 USD for Blockstream Ledger Nano S x 1, \"Back In My Day\" Sticker x 2, \"I Got Lightning Working\" Sticker x 2 and 1 more items".to_owned()) @@ -267,9 +327,15 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .amount_milli_satoshis(2_500_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102" - ).unwrap()) + .payment_hash(lightning_invoice::PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102", + ) + .unwrap(), + ) + .unwrap(), + )) .description("coffee beans".to_owned()) .build_raw() .unwrap() @@ -288,9 +354,15 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .amount_milli_satoshis(2_500_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102" - ).unwrap()) + .payment_hash(lightning_invoice::PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102", + ) + .unwrap(), + ) + .unwrap(), + )) .description("coffee beans".to_owned()) .build_raw() .unwrap() @@ -309,9 +381,15 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { .amount_milli_satoshis(2_500_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102" - ).unwrap()) + .payment_hash(lightning_invoice::PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102", + ) + .unwrap(), + ) + .unwrap(), + )) .description("coffee beans".to_owned()) .build_raw() .unwrap() @@ -329,9 +407,15 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { InvoiceBuilder::new(Currency::Bitcoin) .amount_milli_satoshis(1_000_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) - .payment_hash(sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102" - ).unwrap()) + .payment_hash(lightning_invoice::PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102", + ) + .unwrap(), + ) + .unwrap(), + )) .description("payment metadata inside".to_owned()) .payment_metadata(>::from_hex("01fafaf0").unwrap()) .require_payment_metadata() @@ -355,9 +439,15 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { InvoiceBuilder::new(Currency::Bitcoin) .amount_milli_satoshis(1_000_000_000) .duration_since_epoch(Duration::from_secs(1496314658)) - .payment_hash(sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102" - ).unwrap()) + .payment_hash(lightning_invoice::PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102", + ) + .unwrap(), + ) + .unwrap(), + )) .description("payment metadata inside".to_owned()) .payment_metadata(>::from_hex("01fafaf0").unwrap()) .require_payment_metadata() @@ -378,9 +468,15 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> { InvoiceBuilder::new(Currency::Bitcoin) .duration_since_epoch(Duration::from_secs(1496314658)) .payment_secret(PaymentSecret([0x11; 32])) - .payment_hash(sha256::Hash::from_str( - "0001020304050607080900010203040506070809000102030405060708090102" - ).unwrap()) + .payment_hash(lightning_invoice::PaymentHash( + <[u8; 32]>::try_from( + Vec::from_hex( + "0001020304050607080900010203040506070809000102030405060708090102", + ) + .unwrap(), + ) + .unwrap(), + )) .description("Please consider supporting this project".to_owned()) .build_raw() .unwrap() diff --git a/lightning-liquidity/tests/lsps2_integration_tests.rs b/lightning-liquidity/tests/lsps2_integration_tests.rs index 45c2891227d..8a607a80241 100644 --- a/lightning-liquidity/tests/lsps2_integration_tests.rs +++ b/lightning-liquidity/tests/lsps2_integration_tests.rs @@ -145,7 +145,7 @@ fn create_jit_invoice( let currency = Network::Bitcoin.into(); let mut invoice_builder = InvoiceBuilder::new(currency) .description(description.to_string()) - .payment_hash(payment_hash) + .payment_hash(PaymentHash(payment_hash.to_byte_array())) .payment_secret(payment_secret) .current_timestamp() .min_final_cltv_expiry_delta(min_final_cltv_expiry_delta.into()) diff --git a/lightning/src/ln/bolt11_payment_tests.rs b/lightning/src/ln/bolt11_payment_tests.rs index 63c5576e333..63861f98945 100644 --- a/lightning/src/ln/bolt11_payment_tests.rs +++ b/lightning/src/ln/bolt11_payment_tests.rs @@ -16,8 +16,6 @@ use crate::ln::msgs::ChannelMessageHandler; use crate::ln::outbound_payment::Bolt11PaymentError; use crate::routing::router::RouteParametersConfig; use crate::sign::{NodeSigner, Recipient}; -use bitcoin::hashes::sha256::Hash as Sha256; -use bitcoin::hashes::Hash; use lightning_invoice::{Bolt11Invoice, Currency, InvoiceBuilder}; use std::time::SystemTime; @@ -39,7 +37,7 @@ fn payment_metadata_end_to_end_for_invoice_with_amount() { let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let invoice = InvoiceBuilder::new(Currency::Bitcoin) .description("test".into()) - .payment_hash(Sha256::from_slice(&payment_hash.0).unwrap()) + .payment_hash(payment_hash) .payment_secret(payment_secret) .duration_since_epoch(timestamp) .min_final_cltv_expiry_delta(144) @@ -108,7 +106,7 @@ fn payment_metadata_end_to_end_for_invoice_with_no_amount() { let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let invoice = InvoiceBuilder::new(Currency::Bitcoin) .description("test".into()) - .payment_hash(Sha256::from_slice(&payment_hash.0).unwrap()) + .payment_hash(payment_hash) .payment_secret(payment_secret) .duration_since_epoch(timestamp) .min_final_cltv_expiry_delta(144) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 0f9adfcc51a..202483cf775 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -13309,7 +13309,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ let mut invoice = invoice .duration_since_epoch(duration_since_epoch) .payee_pub_key(self.get_our_node_id()) - .payment_hash(Hash::from_slice(&payment_hash.0).unwrap()) + .payment_hash(payment_hash) .payment_secret(payment_secret) .basic_mpp() .min_final_cltv_expiry_delta( @@ -16169,8 +16169,9 @@ where Err(()) => return None, }; + let payment_hash = invoice.payment_hash(); let logger = WithContext::for_payment( - &self.logger, None, None, Some(invoice.payment_hash()), payment_id, + &self.logger, None, None, Some(payment_hash), payment_id, ); if self.config.read().unwrap().manually_handle_bolt12_invoices { diff --git a/lightning/src/ln/invoice_utils.rs b/lightning/src/ln/invoice_utils.rs index e72ea4518a4..9d1ab901327 100644 --- a/lightning/src/ln/invoice_utils.rs +++ b/lightning/src/ln/invoice_utils.rs @@ -18,7 +18,6 @@ use crate::sign::{EntropySource, NodeSigner, Recipient}; use crate::types::payment::PaymentHash; use crate::util::logger::{Logger, Record}; use alloc::collections::{btree_map, BTreeMap}; -use bitcoin::hashes::Hash; use bitcoin::secp256k1::PublicKey; #[cfg(not(feature = "std"))] use core::iter::Iterator; @@ -228,7 +227,7 @@ where let mut invoice = invoice .duration_since_epoch(duration_since_epoch) - .payment_hash(Hash::from_slice(&payment_hash.0).unwrap()) + .payment_hash(payment_hash) .payment_secret(payment_secret) .min_final_cltv_expiry_delta( // Add a buffer of 3 to the delta if present, otherwise use LDK's minimum. @@ -829,7 +828,7 @@ mod test { invoice.description(), Bolt11InvoiceDescriptionRef::Direct(&Description::new("test".to_string()).unwrap()) ); - assert_eq!(invoice.payment_hash(), payment_hash); + assert_eq!(invoice.payment_hash().0, payment_hash.0); } #[cfg(not(feature = "std"))] @@ -1257,7 +1256,8 @@ mod test { Duration::from_secs(genesis_timestamp), ) .unwrap(); - let (payment_hash, payment_secret) = (invoice.payment_hash(), *invoice.payment_secret()); + let payment_hash = invoice.payment_hash(); + let payment_secret = *invoice.payment_secret(); let payment_preimage = if user_generated_pmt_hash { user_payment_preimage } else {