From cf0a90b8c0af3bed51d73df0e59954c6cdac6e09 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Wed, 8 Feb 2023 09:09:44 -0600 Subject: [PATCH] `Fallback`: add `Address` getter and use bitcoin types --- lightning-invoice/Cargo.toml | 1 + lightning-invoice/src/de.rs | 37 +++++++++++++------------ lightning-invoice/src/lib.rs | 46 +++++++++++++++++++++++++------ lightning-invoice/src/ser.rs | 2 +- lightning-invoice/tests/ser_de.rs | 13 +++++---- 5 files changed, 67 insertions(+), 32 deletions(-) diff --git a/lightning-invoice/Cargo.toml b/lightning-invoice/Cargo.toml index 53a36faba..0e92355c9 100644 --- a/lightning-invoice/Cargo.toml +++ b/lightning-invoice/Cargo.toml @@ -27,6 +27,7 @@ num-traits = { version = "0.2.8", default-features = false } bitcoin_hashes = { version = "0.11", default-features = false } hashbrown = { version = "0.8", optional = true } serde = { version = "1.0.118", optional = true } +bitcoin = { version = "0.29.0", default-features = false } [dev-dependencies] lightning = { version = "0.0.113", path = "../lightning", default-features = false, features = ["_test_utils"] } diff --git a/lightning-invoice/src/de.rs b/lightning-invoice/src/de.rs index 92c1cb5c2..529d81a66 100644 --- a/lightning-invoice/src/de.rs +++ b/lightning-invoice/src/de.rs @@ -1,5 +1,6 @@ #[cfg(feature = "std")] use std::error; +use core::convert::TryFrom; use core::fmt; use core::fmt::{Display, Formatter}; use core::num::ParseIntError; @@ -9,6 +10,8 @@ use core::str::FromStr; use bech32; use bech32::{u5, FromBase32}; +use bitcoin::{PubkeyHash, ScriptHash}; +use bitcoin::util::address::WitnessVersion; use bitcoin_hashes::Hash; use bitcoin_hashes::sha256; use crate::prelude::*; @@ -552,27 +555,24 @@ impl FromBase32 for Fallback { if bytes.len() < 2 || bytes.len() > 40 { return Err(ParseError::InvalidSegWitProgramLength); } - + let version = WitnessVersion::try_from(version).expect("0 through 16 are valid SegWit versions"); Ok(Fallback::SegWitProgram { version: version, program: bytes }) }, 17 => { - if bytes.len() != 20 { - return Err(ParseError::InvalidPubKeyHashLength); - } - //TODO: refactor once const generics are available - let mut pkh = [0u8; 20]; - pkh.copy_from_slice(&bytes); + let pkh = match PubkeyHash::from_slice(&bytes) { + Ok(pkh) => pkh, + Err(bitcoin_hashes::Error::InvalidLength(_, _)) => return Err(ParseError::InvalidPubKeyHashLength), + }; Ok(Fallback::PubKeyHash(pkh)) } 18 => { - if bytes.len() != 20 { - return Err(ParseError::InvalidScriptHashLength); - } - let mut sh = [0u8; 20]; - sh.copy_from_slice(&bytes); + let sh = match ScriptHash::from_slice(&bytes) { + Ok(sh) => sh, + Err(bitcoin_hashes::Error::InvalidLength(_, _)) => return Err(ParseError::InvalidScriptHashLength), + }; Ok(Fallback::ScriptHash(sh)) } _ => Err(ParseError::Skip) @@ -854,26 +854,29 @@ mod test { fn test_parse_fallback() { use crate::Fallback; use bech32::FromBase32; + use bitcoin::{PubkeyHash, ScriptHash}; + use bitcoin::util::address::WitnessVersion; + use bitcoin_hashes::Hash; let cases = vec![ ( from_bech32("3x9et2e20v6pu37c5d9vax37wxq72un98".as_bytes()), - Ok(Fallback::PubKeyHash([ + Ok(Fallback::PubKeyHash(PubkeyHash::from_slice(&[ 0x31, 0x72, 0xb5, 0x65, 0x4f, 0x66, 0x83, 0xc8, 0xfb, 0x14, 0x69, 0x59, 0xd3, 0x47, 0xce, 0x30, 0x3c, 0xae, 0x4c, 0xa7 - ])) + ]).unwrap())) ), ( from_bech32("j3a24vwu6r8ejrss3axul8rxldph2q7z9".as_bytes()), - Ok(Fallback::ScriptHash([ + Ok(Fallback::ScriptHash(ScriptHash::from_slice(&[ 0x8f, 0x55, 0x56, 0x3b, 0x9a, 0x19, 0xf3, 0x21, 0xc2, 0x11, 0xe9, 0xb9, 0xf3, 0x8c, 0xdf, 0x68, 0x6e, 0xa0, 0x78, 0x45 - ])) + ]).unwrap())) ), ( from_bech32("qw508d6qejxtdg4y5r3zarvary0c5xw7k".as_bytes()), Ok(Fallback::SegWitProgram { - version: u5::try_from_u8(0).unwrap(), + version: WitnessVersion::V0, program: Vec::from(&[ 0x75u8, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6 diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index 17b6e2de2..929d6c286 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -45,8 +45,9 @@ extern crate serde; use std::time::SystemTime; use bech32::u5; -use bitcoin_hashes::Hash; -use bitcoin_hashes::sha256; +use bitcoin::{Address, Network, PubkeyHash, ScriptHash}; +use bitcoin::util::address::{Payload, WitnessVersion}; +use bitcoin_hashes::{Hash, sha256}; use lightning::ln::PaymentSecret; use lightning::ln::features::InvoiceFeatures; #[cfg(any(doc, test))] @@ -442,17 +443,16 @@ pub struct ExpiryTime(Duration); #[derive(Clone, Debug, Hash, Eq, PartialEq)] pub struct MinFinalCltvExpiryDelta(pub u64); -// TODO: better types instead onf byte arrays /// Fallback address in case no LN payment is possible #[allow(missing_docs)] #[derive(Clone, Debug, Hash, Eq, PartialEq)] pub enum Fallback { SegWitProgram { - version: u5, + version: WitnessVersion, program: Vec, }, - PubKeyHash([u8; 20]), - ScriptHash([u8; 20]), + PubKeyHash(PubkeyHash), + ScriptHash(ScriptHash), } /// Recoverable signature @@ -1258,6 +1258,33 @@ impl Invoice { self.signed_invoice.fallbacks() } + /// Returns a list of all fallback addresses as [`Address`]es + pub fn fallback_addresses(&self) -> Vec
{ + self.fallbacks().iter().map(|fallback| { + let network = match self.currency() { + Currency::Bitcoin => Network::Bitcoin, + Currency::BitcoinTestnet => Network::Testnet, + Currency::Regtest => Network::Regtest, + Currency::Simnet => Network::Regtest, + Currency::Signet => Network::Signet, + }; + + let payload = match fallback { + Fallback::SegWitProgram { version, program } => { + Payload::WitnessProgram { version: *version, program: program.to_vec() } + } + Fallback::PubKeyHash(pkh) => { + Payload::PubkeyHash(*pkh) + } + Fallback::ScriptHash(sh) => { + Payload::ScriptHash(*sh) + } + }; + + Address { payload, network } + }).collect() + } + /// Returns a list of all routes included in the invoice pub fn private_routes(&self) -> Vec<&PrivateRoute> { self.signed_invoice.private_routes() @@ -1567,6 +1594,7 @@ impl<'de> Deserialize<'de> for Invoice { #[cfg(test)] mod test { + use bitcoin::Script; use bitcoin_hashes::hex::FromHex; use bitcoin_hashes::sha256; @@ -1930,7 +1958,7 @@ mod test { .payee_pub_key(public_key.clone()) .expiry_time(Duration::from_secs(54321)) .min_final_cltv_expiry_delta(144) - .fallback(Fallback::PubKeyHash([0;20])) + .fallback(Fallback::PubKeyHash(PubkeyHash::from_slice(&[0;20]).unwrap())) .private_route(route_1.clone()) .private_route(route_2.clone()) .description_hash(sha256::Hash::from_slice(&[3;32][..]).unwrap()) @@ -1956,7 +1984,9 @@ mod test { assert_eq!(invoice.payee_pub_key(), Some(&public_key)); assert_eq!(invoice.expiry_time(), Duration::from_secs(54321)); assert_eq!(invoice.min_final_cltv_expiry_delta(), 144); - assert_eq!(invoice.fallbacks(), vec![&Fallback::PubKeyHash([0;20])]); + assert_eq!(invoice.fallbacks(), vec![&Fallback::PubKeyHash(PubkeyHash::from_slice(&[0;20]).unwrap())]); + let address = Address::from_script(&Script::new_p2pkh(&PubkeyHash::from_slice(&[0;20]).unwrap()), Network::Testnet).unwrap(); + assert_eq!(invoice.fallback_addresses(), vec![address]); assert_eq!(invoice.private_routes(), vec![&PrivateRoute(route_1), &PrivateRoute(route_2)]); assert_eq!( invoice.description(), diff --git a/lightning-invoice/src/ser.rs b/lightning-invoice/src/ser.rs index f57422891..5f9194615 100644 --- a/lightning-invoice/src/ser.rs +++ b/lightning-invoice/src/ser.rs @@ -329,7 +329,7 @@ impl ToBase32 for Fallback { fn write_base32(&self, writer: &mut W) -> Result<(), ::Err> { match *self { Fallback::SegWitProgram {version: v, program: ref p} => { - writer.write_u5(v)?; + writer.write_u5(Into::::into(v))?; p.write_base32(writer) }, Fallback::PubKeyHash(ref hash) => { diff --git a/lightning-invoice/tests/ser_de.rs b/lightning-invoice/tests/ser_de.rs index 272d9062a..73e2ea838 100644 --- a/lightning-invoice/tests/ser_de.rs +++ b/lightning-invoice/tests/ser_de.rs @@ -5,9 +5,10 @@ extern crate lightning_invoice; extern crate secp256k1; extern crate hex; +use bitcoin::util::address::WitnessVersion; +use bitcoin::{PubkeyHash, ScriptHash}; use bitcoin_hashes::hex::FromHex; use bitcoin_hashes::{sha256, Hash}; -use bech32::u5; use lightning::ln::PaymentSecret; use lightning::routing::gossip::RoutingFees; use lightning::routing::router::{RouteHint, RouteHintHop}; @@ -115,7 +116,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawInvoice, bool, bool)> { .payment_hash(sha256::Hash::from_hex( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) - .fallback(Fallback::PubKeyHash([49, 114, 181, 101, 79, 102, 131, 200, 251, 20, 105, 89, 211, 71, 206, 48, 60, 174, 76, 167])) + .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() .sign(|_| { @@ -137,7 +138,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawInvoice, bool, bool)> { .payment_hash(sha256::Hash::from_hex( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) - .fallback(Fallback::PubKeyHash([4, 182, 31, 125, 193, 234, 13, 201, 148, 36, 70, 76, 196, 6, 77, 197, 100, 217, 30, 137])) + .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(&hex::decode( "029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255" @@ -176,7 +177,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawInvoice, bool, bool)> { .payment_hash(sha256::Hash::from_hex( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) - .fallback(Fallback::ScriptHash([143, 85, 86, 59, 154, 25, 243, 33, 194, 17, 233, 185, 243, 140, 223, 104, 110, 160, 120, 69])) + .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() .sign(|_| { @@ -198,7 +199,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawInvoice, bool, bool)> { .payment_hash(sha256::Hash::from_hex( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) - .fallback(Fallback::SegWitProgram { version: u5::try_from_u8(0).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] }) .build_raw() @@ -222,7 +223,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawInvoice, bool, bool)> { .payment_hash(sha256::Hash::from_hex( "0001020304050607080900010203040506070809000102030405060708090102" ).unwrap()) - .fallback(Fallback::SegWitProgram { version: u5::try_from_u8(0).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] }) .build_raw() -- 2.39.5