mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-02-25 07:17:40 +01:00
Merge pull request #1057 from TheBlueMatt/2021-08-invoice-fails
Fix and modernize lightning-invoice API
This commit is contained in:
commit
6bd1af4f9f
8 changed files with 454 additions and 139 deletions
|
@ -16,4 +16,5 @@ num-traits = "0.2.8"
|
|||
bitcoin_hashes = "0.10"
|
||||
|
||||
[dev-dependencies]
|
||||
hex = "0.3"
|
||||
lightning = { version = "0.0.100", path = "../lightning", features = ["_test_utils"] }
|
||||
|
|
|
@ -77,7 +77,7 @@ mod hrp_sm {
|
|||
} else if ['m', 'u', 'n', 'p'].contains(&read_symbol) {
|
||||
Ok(States::ParseAmountSiPrefix)
|
||||
} else {
|
||||
Err(super::ParseError::MalformedHRP)
|
||||
Err(super::ParseError::UnknownSiPrefix)
|
||||
}
|
||||
},
|
||||
States::ParseAmountSiPrefix => Err(super::ParseError::MalformedHRP),
|
||||
|
@ -209,10 +209,18 @@ impl FromStr for SiPrefix {
|
|||
/// ```
|
||||
/// use lightning_invoice::Invoice;
|
||||
///
|
||||
/// let invoice = "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp\
|
||||
/// l2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d7\
|
||||
/// 3gafnh3cax9rn449d9p5uxz9ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ec\
|
||||
/// ky03ylcqca784w";
|
||||
///
|
||||
/// let invoice = "lnbc100p1psj9jhxdqud3jxktt5w46x7unfv9kz6mn0v3jsnp4q0d3p2sfluzdx45tqcs\
|
||||
/// h2pu5qc7lgq0xs578ngs6s0s68ua4h7cvspp5q6rmq35js88zp5dvwrv9m459tnk2zunwj5jalqtyxqulh0l\
|
||||
/// 5gflssp5nf55ny5gcrfl30xuhzj3nphgj27rstekmr9fw3ny5989s300gyus9qyysgqcqpcrzjqw2sxwe993\
|
||||
/// h5pcm4dxzpvttgza8zhkqxpgffcrf5v25nwpr3cmfg7z54kuqq8rgqqqqqqqq2qqqqq9qq9qrzjqd0ylaqcl\
|
||||
/// j9424x9m8h2vcukcgnm6s56xfgu3j78zyqzhgs4hlpzvznlugqq9vsqqqqqqqlgqqqqqeqq9qrzjqwldmj9d\
|
||||
/// ha74df76zhx6l9we0vjdquygcdt3kssupehe64g6yyp5yz5rhuqqwccqqyqqqqlgqqqqjcqq9qrzjqf9e58a\
|
||||
/// guqr0rcun0ajlvmzq3ek63cw2w282gv3z5uupmuwvgjtq2z55qsqqg6qqqyqqqrtnqqqzq3cqygrzjqvphms\
|
||||
/// ywntrrhqjcraumvc4y6r8v4z5v593trte429v4hredj7ms5z52usqq9ngqqqqqqqlgqqqqqqgq9qrzjq2v0v\
|
||||
/// p62g49p7569ev48cmulecsxe59lvaw3wlxm7r982zxa9zzj7z5l0cqqxusqqyqqqqlgqqqqqzsqygarl9fh3\
|
||||
/// 8s0gyuxjjgux34w75dnc6xp2l35j7es3jd4ugt3lu0xzre26yg5m7ke54n2d5sym4xcmxtl8238xxvw5h5h5\
|
||||
/// j5r6drg6k6zcqj0fcwg";
|
||||
///
|
||||
/// assert!(invoice.parse::<Invoice>().is_ok());
|
||||
/// ```
|
||||
|
@ -228,10 +236,17 @@ impl FromStr for Invoice {
|
|||
/// ```
|
||||
/// use lightning_invoice::*;
|
||||
///
|
||||
/// let invoice = "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp\
|
||||
/// l2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d7\
|
||||
/// 3gafnh3cax9rn449d9p5uxz9ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ec\
|
||||
/// ky03ylcqca784w";
|
||||
/// let invoice = "lnbc100p1psj9jhxdqud3jxktt5w46x7unfv9kz6mn0v3jsnp4q0d3p2sfluzdx45tqcs\
|
||||
/// h2pu5qc7lgq0xs578ngs6s0s68ua4h7cvspp5q6rmq35js88zp5dvwrv9m459tnk2zunwj5jalqtyxqulh0l\
|
||||
/// 5gflssp5nf55ny5gcrfl30xuhzj3nphgj27rstekmr9fw3ny5989s300gyus9qyysgqcqpcrzjqw2sxwe993\
|
||||
/// h5pcm4dxzpvttgza8zhkqxpgffcrf5v25nwpr3cmfg7z54kuqq8rgqqqqqqqq2qqqqq9qq9qrzjqd0ylaqcl\
|
||||
/// j9424x9m8h2vcukcgnm6s56xfgu3j78zyqzhgs4hlpzvznlugqq9vsqqqqqqqlgqqqqqeqq9qrzjqwldmj9d\
|
||||
/// ha74df76zhx6l9we0vjdquygcdt3kssupehe64g6yyp5yz5rhuqqwccqqyqqqqlgqqqqjcqq9qrzjqf9e58a\
|
||||
/// guqr0rcun0ajlvmzq3ek63cw2w282gv3z5uupmuwvgjtq2z55qsqqg6qqqyqqqrtnqqqzq3cqygrzjqvphms\
|
||||
/// ywntrrhqjcraumvc4y6r8v4z5v593trte429v4hredj7ms5z52usqq9ngqqqqqqqlgqqqqqqgq9qrzjq2v0v\
|
||||
/// p62g49p7569ev48cmulecsxe59lvaw3wlxm7r982zxa9zzj7z5l0cqqxusqqyqqqqlgqqqqqzsqygarl9fh3\
|
||||
/// 8s0gyuxjjgux34w75dnc6xp2l35j7es3jd4ugt3lu0xzre26yg5m7ke54n2d5sym4xcmxtl8238xxvw5h5h5\
|
||||
/// j5r6drg6k6zcqj0fcwg";
|
||||
///
|
||||
/// let parsed_1 = invoice.parse::<Invoice>();
|
||||
///
|
||||
|
@ -404,7 +419,7 @@ fn parse_tagged_parts(data: &[u5]) -> Result<Vec<RawTaggedField>, ParseError> {
|
|||
Ok(field) => {
|
||||
parts.push(RawTaggedField::KnownSemantics(field))
|
||||
},
|
||||
Err(ParseError::Skip) => {
|
||||
Err(ParseError::Skip)|Err(ParseError::Bech32Error(bech32::Error::InvalidLength)) => {
|
||||
parts.push(RawTaggedField::UnknownSemantics(field.into()))
|
||||
},
|
||||
Err(e) => {return Err(e)}
|
||||
|
|
|
@ -127,6 +127,7 @@ pub fn check_platform() {
|
|||
///
|
||||
/// ```
|
||||
/// extern crate secp256k1;
|
||||
/// extern crate lightning;
|
||||
/// extern crate lightning_invoice;
|
||||
/// extern crate bitcoin_hashes;
|
||||
///
|
||||
|
@ -136,6 +137,8 @@ pub fn check_platform() {
|
|||
/// use secp256k1::Secp256k1;
|
||||
/// use secp256k1::key::SecretKey;
|
||||
///
|
||||
/// use lightning::ln::PaymentSecret;
|
||||
///
|
||||
/// use lightning_invoice::{Currency, InvoiceBuilder};
|
||||
///
|
||||
/// # fn main() {
|
||||
|
@ -148,10 +151,12 @@ pub fn check_platform() {
|
|||
/// ).unwrap();
|
||||
///
|
||||
/// let payment_hash = sha256::Hash::from_slice(&[0; 32][..]).unwrap();
|
||||
/// let payment_secret = PaymentSecret([42u8; 32]);
|
||||
///
|
||||
/// let invoice = InvoiceBuilder::new(Currency::Bitcoin)
|
||||
/// .description("Coins pls!".into())
|
||||
/// .payment_hash(payment_hash)
|
||||
/// .payment_secret(payment_secret)
|
||||
/// .current_timestamp()
|
||||
/// .min_final_cltv_expiry(144)
|
||||
/// .build_signed(|hash| {
|
||||
|
@ -321,7 +326,7 @@ impl SiPrefix {
|
|||
}
|
||||
|
||||
/// Enum representing the crypto currencies (or networks) supported by this library
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub enum Currency {
|
||||
/// Bitcoin mainnet
|
||||
Bitcoin,
|
||||
|
@ -342,7 +347,7 @@ pub enum Currency {
|
|||
/// Tagged field which may have an unknown tag
|
||||
///
|
||||
/// (C-not exported) as we don't currently support TaggedField
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub enum RawTaggedField {
|
||||
/// Parsed tagged field with known tag
|
||||
KnownSemantics(TaggedField),
|
||||
|
@ -357,7 +362,7 @@ pub enum RawTaggedField {
|
|||
/// (C-not exported) As we don't yet support enum variants with the same name the struct contained
|
||||
/// in the variant.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub enum TaggedField {
|
||||
PaymentHash(Sha256),
|
||||
Description(Description),
|
||||
|
@ -372,18 +377,18 @@ pub enum TaggedField {
|
|||
}
|
||||
|
||||
/// SHA-256 hash
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct Sha256(pub sha256::Hash);
|
||||
|
||||
/// Description string
|
||||
///
|
||||
/// # Invariants
|
||||
/// The description can be at most 639 __bytes__ long
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct Description(String);
|
||||
|
||||
/// Payee public key
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct PayeePubKey(pub PublicKey);
|
||||
|
||||
/// Positive duration that defines when (relatively to the timestamp) in the future the invoice
|
||||
|
@ -393,17 +398,17 @@ pub struct PayeePubKey(pub PublicKey);
|
|||
/// The number of seconds this expiry time represents has to be in the range
|
||||
/// `0...(SYSTEM_TIME_MAX_UNIX_TIMESTAMP - MAX_EXPIRY_TIME)` to avoid overflows when adding it to a
|
||||
/// timestamp
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct ExpiryTime(Duration);
|
||||
|
||||
/// `min_final_cltv_expiry` to use for the last HTLC in the route
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct MinFinalCltvExpiry(pub u64);
|
||||
|
||||
// TODO: better types instead onf byte arrays
|
||||
/// Fallback address in case no LN payment is possible
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub enum Fallback {
|
||||
SegWitProgram {
|
||||
version: u5,
|
||||
|
@ -414,7 +419,7 @@ pub enum Fallback {
|
|||
}
|
||||
|
||||
/// Recoverable signature
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct InvoiceSignature(pub RecoverableSignature);
|
||||
|
||||
/// Private routing information
|
||||
|
@ -422,7 +427,7 @@ pub struct InvoiceSignature(pub RecoverableSignature);
|
|||
/// # Invariants
|
||||
/// The encoded route has to be <1024 5bit characters long (<=639 bytes or <=12 hops)
|
||||
///
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct PrivateRoute(RouteHint);
|
||||
|
||||
/// Tag constants as specified in BOLT11
|
||||
|
@ -480,8 +485,9 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
|
|||
}
|
||||
}
|
||||
|
||||
/// Sets the amount in pico BTC. The optimal SI prefix is choosen automatically.
|
||||
pub fn amount_pico_btc(mut self, amount: u64) -> Self {
|
||||
/// Sets the amount in millisatoshis. The optimal SI prefix is chosen automatically.
|
||||
pub fn amount_milli_satoshis(mut self, amount_msat: u64) -> Self {
|
||||
let amount = amount_msat * 10; // Invoices are denominated in "pico BTC"
|
||||
let biggest_possible_si_prefix = SiPrefix::values_desc()
|
||||
.iter()
|
||||
.find(|prefix| amount % prefix.multiplier() == 0)
|
||||
|
@ -633,7 +639,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T,
|
|||
}
|
||||
}
|
||||
|
||||
impl<S: tb::Bool> InvoiceBuilder<tb::True, tb::True, tb::True, tb::True, S> {
|
||||
impl InvoiceBuilder<tb::True, tb::True, tb::True, tb::True, tb::True> {
|
||||
/// Builds and signs an invoice using the supplied `sign_function`. This function MAY NOT fail
|
||||
/// and MUST produce a recoverable signature valid for the given hash and if applicable also for
|
||||
/// the included payee public key.
|
||||
|
@ -673,6 +679,7 @@ impl<S: tb::Bool> InvoiceBuilder<tb::True, tb::True, tb::True, tb::True, S> {
|
|||
|
||||
invoice.check_field_counts().expect("should be ensured by type signature of builder");
|
||||
invoice.check_feature_bits().expect("should be ensured by type signature of builder");
|
||||
invoice.check_amount().expect("should be ensured by type signature of builder");
|
||||
|
||||
Ok(invoice)
|
||||
}
|
||||
|
@ -1016,35 +1023,54 @@ impl Invoice {
|
|||
return Err(SemanticError::MultipleDescriptions);
|
||||
}
|
||||
|
||||
self.check_payment_secret()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks that there is exactly one payment secret field
|
||||
fn check_payment_secret(&self) -> Result<(), SemanticError> {
|
||||
// "A writer MUST include exactly one `s` field."
|
||||
let payment_secret_count = self.tagged_fields().filter(|&tf| match *tf {
|
||||
TaggedField::PaymentSecret(_) => true,
|
||||
_ => false,
|
||||
}).count();
|
||||
if payment_secret_count < 1 {
|
||||
return Err(SemanticError::NoPaymentSecret);
|
||||
} else if payment_secret_count > 1 {
|
||||
return Err(SemanticError::MultiplePaymentSecrets);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check that amount is a whole number of millisatoshis
|
||||
fn check_amount(&self) -> Result<(), SemanticError> {
|
||||
if let Some(amount_pico_btc) = self.amount_pico_btc() {
|
||||
if amount_pico_btc % 10 != 0 {
|
||||
return Err(SemanticError::ImpreciseAmount);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check that feature bits are set as required
|
||||
fn check_feature_bits(&self) -> Result<(), SemanticError> {
|
||||
// "If the payment_secret feature is set, MUST include exactly one s field."
|
||||
let payment_secret_count = self.tagged_fields().filter(|&tf| match *tf {
|
||||
TaggedField::PaymentSecret(_) => true,
|
||||
_ => false,
|
||||
}).count();
|
||||
if payment_secret_count > 1 {
|
||||
return Err(SemanticError::MultiplePaymentSecrets);
|
||||
}
|
||||
self.check_payment_secret()?;
|
||||
|
||||
// "A writer MUST set an s field if and only if the payment_secret feature is set."
|
||||
let has_payment_secret = payment_secret_count == 1;
|
||||
// (this requirement has been since removed, and we now require the payment secret
|
||||
// feature bit always).
|
||||
let features = self.tagged_fields().find(|&tf| match *tf {
|
||||
TaggedField::Features(_) => true,
|
||||
_ => false,
|
||||
});
|
||||
match features {
|
||||
None if has_payment_secret => Err(SemanticError::InvalidFeatures),
|
||||
None => Ok(()),
|
||||
None => Err(SemanticError::InvalidFeatures),
|
||||
Some(TaggedField::Features(features)) => {
|
||||
if features.supports_payment_secret() && has_payment_secret {
|
||||
Ok(())
|
||||
} else if has_payment_secret {
|
||||
if features.requires_unknown_bits() {
|
||||
Err(SemanticError::InvalidFeatures)
|
||||
} else if features.supports_payment_secret() {
|
||||
} else if !features.supports_payment_secret() {
|
||||
Err(SemanticError::InvalidFeatures)
|
||||
} else {
|
||||
Ok(())
|
||||
|
@ -1059,7 +1085,9 @@ impl Invoice {
|
|||
match self.signed_invoice.recover_payee_pub_key() {
|
||||
Err(secp256k1::Error::InvalidRecoveryId) =>
|
||||
return Err(SemanticError::InvalidRecoveryId),
|
||||
Err(_) => panic!("no other error may occur"),
|
||||
Err(secp256k1::Error::InvalidSignature) =>
|
||||
return Err(SemanticError::InvalidSignature),
|
||||
Err(e) => panic!("no other error may occur, got {:?}", e),
|
||||
Ok(_) => {},
|
||||
}
|
||||
|
||||
|
@ -1074,10 +1102,17 @@ impl Invoice {
|
|||
/// ```
|
||||
/// use lightning_invoice::*;
|
||||
///
|
||||
/// let invoice = "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp\
|
||||
/// l2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d7\
|
||||
/// 3gafnh3cax9rn449d9p5uxz9ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ec\
|
||||
/// ky03ylcqca784w";
|
||||
/// let invoice = "lnbc100p1psj9jhxdqud3jxktt5w46x7unfv9kz6mn0v3jsnp4q0d3p2sfluzdx45tqcs\
|
||||
/// h2pu5qc7lgq0xs578ngs6s0s68ua4h7cvspp5q6rmq35js88zp5dvwrv9m459tnk2zunwj5jalqtyxqulh0l\
|
||||
/// 5gflssp5nf55ny5gcrfl30xuhzj3nphgj27rstekmr9fw3ny5989s300gyus9qyysgqcqpcrzjqw2sxwe993\
|
||||
/// h5pcm4dxzpvttgza8zhkqxpgffcrf5v25nwpr3cmfg7z54kuqq8rgqqqqqqqq2qqqqq9qq9qrzjqd0ylaqcl\
|
||||
/// j9424x9m8h2vcukcgnm6s56xfgu3j78zyqzhgs4hlpzvznlugqq9vsqqqqqqqlgqqqqqeqq9qrzjqwldmj9d\
|
||||
/// ha74df76zhx6l9we0vjdquygcdt3kssupehe64g6yyp5yz5rhuqqwccqqyqqqqlgqqqqjcqq9qrzjqf9e58a\
|
||||
/// guqr0rcun0ajlvmzq3ek63cw2w282gv3z5uupmuwvgjtq2z55qsqqg6qqqyqqqrtnqqqzq3cqygrzjqvphms\
|
||||
/// ywntrrhqjcraumvc4y6r8v4z5v593trte429v4hredj7ms5z52usqq9ngqqqqqqqlgqqqqqqgq9qrzjq2v0v\
|
||||
/// p62g49p7569ev48cmulecsxe59lvaw3wlxm7r982zxa9zzj7z5l0cqqxusqqyqqqqlgqqqqqzsqygarl9fh3\
|
||||
/// 8s0gyuxjjgux34w75dnc6xp2l35j7es3jd4ugt3lu0xzre26yg5m7ke54n2d5sym4xcmxtl8238xxvw5h5h5\
|
||||
/// j5r6drg6k6zcqj0fcwg";
|
||||
///
|
||||
/// let signed = invoice.parse::<SignedRawInvoice>().unwrap();
|
||||
///
|
||||
|
@ -1090,6 +1125,7 @@ impl Invoice {
|
|||
invoice.check_field_counts()?;
|
||||
invoice.check_feature_bits()?;
|
||||
invoice.check_signature()?;
|
||||
invoice.check_amount()?;
|
||||
|
||||
Ok(invoice)
|
||||
}
|
||||
|
@ -1130,8 +1166,8 @@ impl Invoice {
|
|||
}
|
||||
|
||||
/// Get the payment secret if one was included in the invoice
|
||||
pub fn payment_secret(&self) -> Option<&PaymentSecret> {
|
||||
self.signed_invoice.payment_secret()
|
||||
pub fn payment_secret(&self) -> &PaymentSecret {
|
||||
self.signed_invoice.payment_secret().expect("was checked by constructor")
|
||||
}
|
||||
|
||||
/// Get the invoice features if they were included in the invoice
|
||||
|
@ -1388,6 +1424,10 @@ pub enum SemanticError {
|
|||
/// The invoice contains multiple descriptions and/or description hashes which isn't allowed
|
||||
MultipleDescriptions,
|
||||
|
||||
/// The invoice is missing the mandatory payment secret, which all modern lightning nodes
|
||||
/// should provide.
|
||||
NoPaymentSecret,
|
||||
|
||||
/// The invoice contains multiple payment secrets
|
||||
MultiplePaymentSecrets,
|
||||
|
||||
|
@ -1399,6 +1439,9 @@ pub enum SemanticError {
|
|||
|
||||
/// The invoice's signature is invalid
|
||||
InvalidSignature,
|
||||
|
||||
/// The invoice's amount was not a whole number of millisatoshis
|
||||
ImpreciseAmount,
|
||||
}
|
||||
|
||||
impl Display for SemanticError {
|
||||
|
@ -1408,10 +1451,12 @@ impl Display for SemanticError {
|
|||
SemanticError::MultiplePaymentHashes => f.write_str("The invoice has multiple payment hashes which isn't allowed"),
|
||||
SemanticError::NoDescription => f.write_str("No description or description hash are part of the invoice"),
|
||||
SemanticError::MultipleDescriptions => f.write_str("The invoice contains multiple descriptions and/or description hashes which isn't allowed"),
|
||||
SemanticError::NoPaymentSecret => f.write_str("The invoice is missing the mandatory payment secret"),
|
||||
SemanticError::MultiplePaymentSecrets => f.write_str("The invoice contains multiple payment secrets"),
|
||||
SemanticError::InvalidFeatures => f.write_str("The invoice's features are invalid"),
|
||||
SemanticError::InvalidRecoveryId => f.write_str("The recovery id doesn't fit the signature/pub key"),
|
||||
SemanticError::InvalidSignature => f.write_str("The invoice's signature is invalid"),
|
||||
SemanticError::ImpreciseAmount => f.write_str("The invoice's amount was not a whole number of millisatoshis"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1623,7 +1668,7 @@ mod test {
|
|||
let invoice = invoice_template.clone();
|
||||
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_recoverable(hash, &private_key)))
|
||||
}.unwrap();
|
||||
assert!(Invoice::from_signed(invoice).is_ok());
|
||||
assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::NoPaymentSecret));
|
||||
|
||||
// No payment secret or feature bits
|
||||
let invoice = {
|
||||
|
@ -1631,7 +1676,7 @@ mod test {
|
|||
invoice.data.tagged_fields.push(Features(InvoiceFeatures::empty()).into());
|
||||
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_recoverable(hash, &private_key)))
|
||||
}.unwrap();
|
||||
assert!(Invoice::from_signed(invoice).is_ok());
|
||||
assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::NoPaymentSecret));
|
||||
|
||||
// Missing payment secret
|
||||
let invoice = {
|
||||
|
@ -1639,7 +1684,7 @@ mod test {
|
|||
invoice.data.tagged_fields.push(Features(InvoiceFeatures::known()).into());
|
||||
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_recoverable(hash, &private_key)))
|
||||
}.unwrap();
|
||||
assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::InvalidFeatures));
|
||||
assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::NoPaymentSecret));
|
||||
|
||||
// Multiple payment secrets
|
||||
let invoice = {
|
||||
|
@ -1661,7 +1706,7 @@ mod test {
|
|||
.current_timestamp();
|
||||
|
||||
let invoice = builder.clone()
|
||||
.amount_pico_btc(15000)
|
||||
.amount_milli_satoshis(1500)
|
||||
.build_raw()
|
||||
.unwrap();
|
||||
|
||||
|
@ -1670,7 +1715,7 @@ mod test {
|
|||
|
||||
|
||||
let invoice = builder.clone()
|
||||
.amount_pico_btc(1500)
|
||||
.amount_milli_satoshis(150)
|
||||
.build_raw()
|
||||
.unwrap();
|
||||
|
||||
|
@ -1725,6 +1770,7 @@ mod test {
|
|||
|
||||
let sign_error_res = builder.clone()
|
||||
.description("Test".into())
|
||||
.payment_secret(PaymentSecret([0; 32]))
|
||||
.try_build_signed(|_| {
|
||||
Err("ImaginaryError")
|
||||
});
|
||||
|
@ -1801,7 +1847,7 @@ mod test {
|
|||
]);
|
||||
|
||||
let builder = InvoiceBuilder::new(Currency::BitcoinTestnet)
|
||||
.amount_pico_btc(123)
|
||||
.amount_milli_satoshis(123)
|
||||
.timestamp(UNIX_EPOCH + Duration::from_secs(1234567))
|
||||
.payee_pub_key(public_key.clone())
|
||||
.expiry_time(Duration::from_secs(54321))
|
||||
|
@ -1821,7 +1867,7 @@ mod test {
|
|||
assert!(invoice.check_signature().is_ok());
|
||||
assert_eq!(invoice.tagged_fields().count(), 10);
|
||||
|
||||
assert_eq!(invoice.amount_pico_btc(), Some(123));
|
||||
assert_eq!(invoice.amount_pico_btc(), Some(1230));
|
||||
assert_eq!(invoice.currency(), Currency::BitcoinTestnet);
|
||||
assert_eq!(
|
||||
invoice.timestamp().duration_since(UNIX_EPOCH).unwrap().as_secs(),
|
||||
|
@ -1837,7 +1883,7 @@ mod test {
|
|||
InvoiceDescription::Hash(&Sha256(sha256::Hash::from_slice(&[3;32][..]).unwrap()))
|
||||
);
|
||||
assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&[21;32][..]).unwrap());
|
||||
assert_eq!(invoice.payment_secret(), Some(&PaymentSecret([42; 32])));
|
||||
assert_eq!(invoice.payment_secret(), &PaymentSecret([42; 32]));
|
||||
assert_eq!(invoice.features(), Some(&InvoiceFeatures::known()));
|
||||
|
||||
let raw_invoice = builder.build_raw().unwrap();
|
||||
|
@ -1853,6 +1899,7 @@ mod test {
|
|||
let signed_invoice = InvoiceBuilder::new(Currency::Bitcoin)
|
||||
.description("Test".into())
|
||||
.payment_hash(sha256::Hash::from_slice(&[0;32][..]).unwrap())
|
||||
.payment_secret(PaymentSecret([0; 32]))
|
||||
.current_timestamp()
|
||||
.build_raw()
|
||||
.unwrap()
|
||||
|
|
|
@ -68,7 +68,7 @@ where
|
|||
.basic_mpp()
|
||||
.min_final_cltv_expiry(MIN_FINAL_CLTV_EXPIRY.into());
|
||||
if let Some(amt) = amt_msat {
|
||||
invoice = invoice.amount_pico_btc(amt * 10);
|
||||
invoice = invoice.amount_milli_satoshis(amt);
|
||||
}
|
||||
for hint in route_hints {
|
||||
invoice = invoice.private_route(hint);
|
||||
|
@ -132,7 +132,7 @@ mod test {
|
|||
let payment_event = {
|
||||
let mut payment_hash = PaymentHash([0; 32]);
|
||||
payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]);
|
||||
nodes[0].node.send_payment(&route, payment_hash, &Some(invoice.payment_secret().unwrap().clone())).unwrap();
|
||||
nodes[0].node.send_payment(&route, payment_hash, &Some(invoice.payment_secret().clone())).unwrap();
|
||||
let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap();
|
||||
assert_eq!(added_monitors.len(), 1);
|
||||
added_monitors.clear();
|
||||
|
|
|
@ -1,27 +1,30 @@
|
|||
extern crate bech32;
|
||||
extern crate bitcoin_hashes;
|
||||
extern crate lightning;
|
||||
extern crate lightning_invoice;
|
||||
extern crate secp256k1;
|
||||
extern crate hex;
|
||||
|
||||
use bitcoin_hashes::hex::FromHex;
|
||||
use bitcoin_hashes::sha256;
|
||||
use bitcoin_hashes::{sha256, Hash};
|
||||
use bech32::u5;
|
||||
use lightning::ln::PaymentSecret;
|
||||
use lightning::routing::router::{RouteHint, RouteHintHop};
|
||||
use lightning::routing::network_graph::RoutingFees;
|
||||
use lightning_invoice::*;
|
||||
use secp256k1::Secp256k1;
|
||||
use secp256k1::key::SecretKey;
|
||||
use secp256k1::PublicKey;
|
||||
use secp256k1::recovery::{RecoverableSignature, RecoveryId};
|
||||
use std::collections::HashSet;
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
use std::str::FromStr;
|
||||
|
||||
// TODO: add more of the examples from BOLT11 and generate ones causing SemanticErrors
|
||||
|
||||
fn get_test_tuples() -> Vec<(String, SignedRawInvoice, Option<SemanticError>)> {
|
||||
fn get_test_tuples() -> Vec<(String, SignedRawInvoice, bool, bool)> {
|
||||
vec![
|
||||
(
|
||||
"lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmw\
|
||||
wd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d73gafnh3cax9rn449d9p5uxz9\
|
||||
ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ecky03ylcqca784w".to_owned(),
|
||||
"lnbc1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq9qrsgq357wnc5r2ueh7ck6q93dj32dlqnls087fxdwk8qakdyafkq3yap9us6v52vjjsrvywa6rt52cm9r9zqt8r2t7mlcwspyetp5h2tztugp9lfyql".to_owned(),
|
||||
InvoiceBuilder::new(Currency::Bitcoin)
|
||||
.timestamp(UNIX_EPOCH + Duration::from_secs(1496314658))
|
||||
.payment_secret(PaymentSecret([0x11; 32]))
|
||||
.payment_hash(sha256::Hash::from_hex(
|
||||
"0001020304050607080900010203040506070809000102030405060708090102"
|
||||
).unwrap())
|
||||
|
@ -30,26 +33,19 @@ fn get_test_tuples() -> Vec<(String, SignedRawInvoice, Option<SemanticError>)> {
|
|||
.unwrap()
|
||||
.sign(|_| {
|
||||
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()
|
||||
&hex::decode("8d3ce9e28357337f62da0162d9454df827f83cfe499aeb1c1db349d4d81127425e434ca29929406c23bba1ae8ac6ca32880b38d4bf6ff874024cac34ba9625f1").unwrap(),
|
||||
RecoveryId::from_i32(1).unwrap()
|
||||
)
|
||||
}).unwrap(),
|
||||
None
|
||||
false, // Same features as set in InvoiceBuilder
|
||||
false, // No unknown fields
|
||||
),
|
||||
(
|
||||
"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3\
|
||||
k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch\
|
||||
9zw97j25emudupq63nyw24cg27h2rspfj9srp".to_owned(),
|
||||
"lnbc2500u1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpu9qrsgquk0rl77nj30yxdy8j9vdx85fkpmdla2087ne0xh8nhedh8w27kyke0lp53ut353s06fv3qfegext0eh0ymjpf39tuven09sam30g4vgpfna3rh".to_owned(),
|
||||
InvoiceBuilder::new(Currency::Bitcoin)
|
||||
.amount_pico_btc(2500000000)
|
||||
.amount_milli_satoshis(250_000_000)
|
||||
.timestamp(UNIX_EPOCH + Duration::from_secs(1496314658))
|
||||
.payment_secret(PaymentSecret([0x11; 32]))
|
||||
.payment_hash(sha256::Hash::from_hex(
|
||||
"0001020304050607080900010203040506070809000102030405060708090102"
|
||||
).unwrap())
|
||||
|
@ -59,93 +55,341 @@ fn get_test_tuples() -> Vec<(String, SignedRawInvoice, Option<SemanticError>)> {
|
|||
.unwrap()
|
||||
.sign(|_| {
|
||||
RecoverableSignature::from_compact(
|
||||
& [
|
||||
0xe8, 0x96, 0x39, 0xba, 0x68, 0x14, 0xe3, 0x66, 0x89, 0xd4, 0xb9, 0x1b,
|
||||
0xf1, 0x25, 0xf1, 0x03, 0x51, 0xb5, 0x5d, 0xa0, 0x57, 0xb0, 0x06, 0x47,
|
||||
0xa8, 0xda, 0xba, 0xeb, 0x8a, 0x90, 0xc9, 0x5f, 0x16, 0x0f, 0x9d, 0x5a,
|
||||
0x6e, 0x0f, 0x79, 0xd1, 0xfc, 0x2b, 0x96, 0x42, 0x38, 0xb9, 0x44, 0xe2,
|
||||
0xfa, 0x4a, 0xa6, 0x77, 0xc6, 0xf0, 0x20, 0xd4, 0x66, 0x47, 0x2a, 0xb8,
|
||||
0x42, 0xbd, 0x75, 0x0e
|
||||
],
|
||||
&hex::decode("e59e3ffbd3945e4334879158d31e89b076dff54f3fa7979ae79df2db9dcaf5896cbfe1a478b8d2307e92c88139464cb7e6ef26e414c4abe33337961ddc5e8ab1").unwrap(),
|
||||
RecoveryId::from_i32(1).unwrap()
|
||||
)
|
||||
}).unwrap(),
|
||||
None
|
||||
false, // Same features as set in InvoiceBuilder
|
||||
false, // No unknown fields
|
||||
),
|
||||
(
|
||||
"lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qq\
|
||||
dhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqscc6gd6ql3jrc5yzme8v4ntcewwz5cnw92tz0pc8qcuufvq7k\
|
||||
hhr8wpald05e92xw006sq94mg8v2ndf4sefvf9sygkshp5zfem29trqq2yxxz7".to_owned(),
|
||||
"lnbc2500u1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpu9qrsgqhtjpauu9ur7fw2thcl4y9vfvh4m9wlfyz2gem29g5ghe2aak2pm3ps8fdhtceqsaagty2vph7utlgj48u0ged6a337aewvraedendscp573dxr".to_owned(),
|
||||
InvoiceBuilder::new(Currency::Bitcoin)
|
||||
.amount_pico_btc(20000000000)
|
||||
.amount_milli_satoshis(250_000_000)
|
||||
.timestamp(UNIX_EPOCH + Duration::from_secs(1496314658))
|
||||
.payment_secret(PaymentSecret([0x11; 32]))
|
||||
.payment_hash(sha256::Hash::from_hex(
|
||||
"0001020304050607080900010203040506070809000102030405060708090102"
|
||||
).unwrap())
|
||||
.description_hash(sha256::Hash::from_hex(
|
||||
"3925b6f67e2c340036ed12093dd44e0368df1b6ea26c53dbe4811f58fd5db8c1"
|
||||
.description("ナンセンス 1杯".to_owned())
|
||||
.expiry_time(Duration::from_secs(60))
|
||||
.build_raw()
|
||||
.unwrap()
|
||||
.sign(|_| {
|
||||
RecoverableSignature::from_compact(
|
||||
&hex::decode("bae41ef385e0fc972977c7ea42b12cbd76577d2412919da8a8a22f9577b6507710c0e96dd78c821dea16453037f717f44aa7e3d196ebb18fbb97307dcb7336c3").unwrap(),
|
||||
RecoveryId::from_i32(1).unwrap()
|
||||
)
|
||||
}).unwrap(),
|
||||
false, // Same features as set in InvoiceBuilder
|
||||
false, // No unknown fields
|
||||
),
|
||||
(
|
||||
"lnbc20m1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qrsgq7ea976txfraylvgzuxs8kgcw23ezlrszfnh8r6qtfpr6cxga50aj6txm9rxrydzd06dfeawfk6swupvz4erwnyutnjq7x39ymw6j38gp7ynn44".to_owned(),
|
||||
InvoiceBuilder::new(Currency::Bitcoin)
|
||||
.amount_milli_satoshis(2_000_000_000)
|
||||
.timestamp(UNIX_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_hex(
|
||||
"0001020304050607080900010203040506070809000102030405060708090102"
|
||||
).unwrap())
|
||||
.build_raw()
|
||||
.unwrap()
|
||||
.sign(|_| {
|
||||
RecoverableSignature::from_compact(
|
||||
& [
|
||||
0xc6, 0x34, 0x86, 0xe8, 0x1f, 0x8c, 0x87, 0x8a, 0x10, 0x5b, 0xc9, 0xd9,
|
||||
0x59, 0xaf, 0x19, 0x73, 0x85, 0x4c, 0x4d, 0xc5, 0x52, 0xc4, 0xf0, 0xe0,
|
||||
0xe0, 0xc7, 0x38, 0x96, 0x03, 0xd6, 0xbd, 0xc6, 0x77, 0x07, 0xbf, 0x6b,
|
||||
0xe9, 0x92, 0xa8, 0xce, 0x7b, 0xf5, 0x00, 0x16, 0xbb, 0x41, 0xd8, 0xa9,
|
||||
0xb5, 0x35, 0x86, 0x52, 0xc4, 0x96, 0x04, 0x45, 0xa1, 0x70, 0xd0, 0x49,
|
||||
0xce, 0xd4, 0x55, 0x8c
|
||||
],
|
||||
RecoveryId::from_i32(0).unwrap()
|
||||
&hex::decode("f67a5f696648fa4fb102e1a07b230e54722f8e024cee71e80b4847ac191da3fb2d2cdb28cc32344d7e9a9cf5c9b6a0ee0582ae46e9938b9c81e344a4dbb5289d").unwrap(),
|
||||
RecoveryId::from_i32(1).unwrap()
|
||||
)
|
||||
}).unwrap(),
|
||||
None
|
||||
false, // Same features as set in InvoiceBuilder
|
||||
false, // No unknown fields
|
||||
),
|
||||
(
|
||||
"lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp59g4z52329g4z52329g4z52329g4z52329g4z52329g4z52329g4q9qrsgqzfhag3vsafx4e5qssalvw4rn0phsvpp3e5h2xxyk9l8fxsutvndx9t840dqvdrlu2gqmk0q8apqrgnjy9amc07hmjl9e9yzqjks5w2gqgjnyms".to_owned(),
|
||||
InvoiceBuilder::new(Currency::Bitcoin)
|
||||
"lntb20m1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygshp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfpp3x9et2e20v6pu37c5d9vax37wxq72un989qrsgqdj545axuxtnfemtpwkc45hx9d2ft7x04mt8q7y6t0k2dge9e7h8kpy9p34ytyslj3yu569aalz2xdk8xkd7ltxqld94u8h2esmsmacgpghe9k8".to_owned(),
|
||||
InvoiceBuilder::new(Currency::BitcoinTestnet)
|
||||
.amount_milli_satoshis(2_000_000_000)
|
||||
.timestamp(UNIX_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_hex(
|
||||
"0001020304050607080900010203040506070809000102030405060708090102"
|
||||
).unwrap())
|
||||
.description("coffee beans".to_string())
|
||||
.amount_pico_btc(20000000000)
|
||||
.timestamp(UNIX_EPOCH + Duration::from_secs(1496314658))
|
||||
.payment_secret(PaymentSecret([42; 32]))
|
||||
.fallback(Fallback::PubKeyHash([49, 114, 181, 101, 79, 102, 131, 200, 251, 20, 105, 89, 211, 71, 206, 48, 60, 174, 76, 167]))
|
||||
.build_raw()
|
||||
.unwrap()
|
||||
.sign::<_, ()>(|msg_hash| {
|
||||
let privkey = SecretKey::from_slice(&[41; 32]).unwrap();
|
||||
let secp_ctx = Secp256k1::new();
|
||||
Ok(secp_ctx.sign_recoverable(msg_hash, &privkey))
|
||||
.sign(|_| {
|
||||
RecoverableSignature::from_compact(
|
||||
&hex::decode("6ca95a74dc32e69ced6175b15a5cc56a92bf19f5dace0f134b7d94d464b9f5cf6090a18d48b243f289394d17bdf89466d8e6b37df5981f696bc3dd5986e1bee1").unwrap(),
|
||||
RecoveryId::from_i32(1).unwrap()
|
||||
)
|
||||
}).unwrap(),
|
||||
false, // Same features as set in InvoiceBuilder
|
||||
false, // No unknown fields
|
||||
),
|
||||
(
|
||||
"lnbc20m1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3qjmp7lwpagxun9pygexvgpjdc4jdj85fr9yq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4grqszsvpcgpy9qqqqqqgqqqqq7qqzq9qrsgqdfjcdk6w3ak5pca9hwfwfh63zrrz06wwfya0ydlzpgzxkn5xagsqz7x9j4jwe7yj7vaf2k9lqsdk45kts2fd0fkr28am0u4w95tt2nsq76cqw0".to_owned(),
|
||||
InvoiceBuilder::new(Currency::Bitcoin)
|
||||
.amount_milli_satoshis(2_000_000_000)
|
||||
.timestamp(UNIX_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_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]))
|
||||
.private_route(RouteHint(vec![RouteHintHop {
|
||||
src_node_id: PublicKey::from_slice(&hex::decode(
|
||||
"029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255"
|
||||
).unwrap()).unwrap(),
|
||||
short_channel_id: (66051 << 40) | (263430 << 16) | 1800,
|
||||
fees: RoutingFees { base_msat: 1, proportional_millionths: 20 },
|
||||
cltv_expiry_delta: 3,
|
||||
htlc_maximum_msat: None, htlc_minimum_msat: None,
|
||||
}, RouteHintHop {
|
||||
src_node_id: PublicKey::from_slice(&hex::decode(
|
||||
"039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255"
|
||||
).unwrap()).unwrap(),
|
||||
short_channel_id: (197637 << 40) | (395016 << 16) | 2314,
|
||||
fees: RoutingFees { base_msat: 2, proportional_millionths: 30 },
|
||||
cltv_expiry_delta: 4,
|
||||
htlc_maximum_msat: None, htlc_minimum_msat: None,
|
||||
}]))
|
||||
.build_raw()
|
||||
.unwrap()
|
||||
.sign(|_| {
|
||||
RecoverableSignature::from_compact(
|
||||
&hex::decode("6a6586db4e8f6d40e3a5bb92e4df5110c627e9ce493af237e20a046b4e86ea200178c59564ecf892f33a9558bf041b6ad2cb8292d7a6c351fbb7f2ae2d16b54e").unwrap(),
|
||||
RecoveryId::from_i32(0).unwrap()
|
||||
)
|
||||
}).unwrap(),
|
||||
false, // Same features as set in InvoiceBuilder
|
||||
false, // No unknown fields
|
||||
),
|
||||
(
|
||||
"lnbc20m1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygshp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfppj3a24vwu6r8ejrss3axul8rxldph2q7z99qrsgqz6qsgww34xlatfj6e3sngrwfy3ytkt29d2qttr8qz2mnedfqysuqypgqex4haa2h8fx3wnypranf3pdwyluftwe680jjcfp438u82xqphf75ym".to_owned(),
|
||||
InvoiceBuilder::new(Currency::Bitcoin)
|
||||
.amount_milli_satoshis(2_000_000_000)
|
||||
.timestamp(UNIX_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_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]))
|
||||
.build_raw()
|
||||
.unwrap()
|
||||
.sign(|_| {
|
||||
RecoverableSignature::from_compact(
|
||||
&hex::decode("16810439d1a9bfd5a65acc61340dc92448bb2d456a80b58ce012b73cb5202438020500c9ab7ef5573a4d174c811f669885ae27f895bb3a3be52c243589f87518").unwrap(),
|
||||
RecoveryId::from_i32(1).unwrap()
|
||||
)
|
||||
}).unwrap(),
|
||||
false, // Same features as set in InvoiceBuilder
|
||||
false, // No unknown fields
|
||||
),
|
||||
(
|
||||
"lnbc20m1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygshp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfppqw508d6qejxtdg4y5r3zarvary0c5xw7k9qrsgqt29a0wturnys2hhxpner2e3plp6jyj8qx7548zr2z7ptgjjc7hljm98xhjym0dg52sdrvqamxdezkmqg4gdrvwwnf0kv2jdfnl4xatsqmrnsse".to_owned(),
|
||||
InvoiceBuilder::new(Currency::Bitcoin)
|
||||
.amount_milli_satoshis(2_000_000_000)
|
||||
.timestamp(UNIX_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_hex(
|
||||
"0001020304050607080900010203040506070809000102030405060708090102"
|
||||
).unwrap())
|
||||
.fallback(Fallback::SegWitProgram { version: u5::try_from_u8(0).unwrap(),
|
||||
program: vec![117, 30, 118, 232, 25, 145, 150, 212, 84, 148, 28, 69, 209, 179, 163, 35, 241, 67, 59, 214]
|
||||
})
|
||||
.unwrap(),
|
||||
None
|
||||
)
|
||||
.build_raw()
|
||||
.unwrap()
|
||||
.sign(|_| {
|
||||
RecoverableSignature::from_compact(
|
||||
&hex::decode("5a8bd7b97c1cc9055ee60cf2356621f8752248e037a953886a1782b44a58f5ff2d94e6bc89b7b514541a3603bb33722b6c08aa1a3639d34becc549a99fea6eae").unwrap(),
|
||||
RecoveryId::from_i32(0).unwrap()
|
||||
)
|
||||
}).unwrap(),
|
||||
false, // Same features as set in InvoiceBuilder
|
||||
false, // No unknown fields
|
||||
),
|
||||
(
|
||||
"lnbc20m1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygshp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfp4qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q9qrsgq9vlvyj8cqvq6ggvpwd53jncp9nwc47xlrsnenq2zp70fq83qlgesn4u3uyf4tesfkkwwfg3qs54qe426hp3tz7z6sweqdjg05axsrjqp9yrrwc".to_owned(),
|
||||
InvoiceBuilder::new(Currency::Bitcoin)
|
||||
.amount_milli_satoshis(2_000_000_000)
|
||||
.timestamp(UNIX_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_hex(
|
||||
"0001020304050607080900010203040506070809000102030405060708090102"
|
||||
).unwrap())
|
||||
.fallback(Fallback::SegWitProgram { version: u5::try_from_u8(0).unwrap(),
|
||||
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()
|
||||
.unwrap()
|
||||
.sign(|_| {
|
||||
RecoverableSignature::from_compact(
|
||||
&hex::decode("2b3ec248f80301a421817369194f012cdd8af8df1c279981420f9e901e20fa3309d791e11355e609b59ce4a220852a0cd55ab862b1785a83b206c90fa74d01c8").unwrap(),
|
||||
RecoveryId::from_i32(1).unwrap()
|
||||
)
|
||||
}).unwrap(),
|
||||
false, // Same features as set in InvoiceBuilder
|
||||
false, // No unknown fields
|
||||
),
|
||||
(
|
||||
"lnbc9678785340p1pwmna7lpp5gc3xfm08u9qy06djf8dfflhugl6p7lgza6dsjxq454gxhj9t7a0sd8dgfkx7cmtwd68yetpd5s9xar0wfjn5gpc8qhrsdfq24f5ggrxdaezqsnvda3kkum5wfjkzmfqf3jkgem9wgsyuctwdus9xgrcyqcjcgpzgfskx6eqf9hzqnteypzxz7fzypfhg6trddjhygrcyqezcgpzfysywmm5ypxxjemgw3hxjmn8yptk7untd9hxwg3q2d6xjcmtv4ezq7pqxgsxzmnyyqcjqmt0wfjjq6t5v4khxsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsxqyjw5qcqp2rzjq0gxwkzc8w6323m55m4jyxcjwmy7stt9hwkwe2qxmy8zpsgg7jcuwz87fcqqeuqqqyqqqqlgqqqqn3qq9q9qrsgqrvgkpnmps664wgkp43l22qsgdw4ve24aca4nymnxddlnp8vh9v2sdxlu5ywdxefsfvm0fq3sesf08uf6q9a2ke0hc9j6z6wlxg5z5kqpu2v9wz".to_owned(),
|
||||
InvoiceBuilder::new(Currency::Bitcoin)
|
||||
.amount_milli_satoshis(967878534)
|
||||
.timestamp(UNIX_EPOCH + Duration::from_secs(1572468703))
|
||||
.payment_secret(PaymentSecret([0x11; 32]))
|
||||
.payment_hash(sha256::Hash::from_hex(
|
||||
"462264ede7e14047e9b249da94fefc47f41f7d02ee9b091815a5506bc8abf75f"
|
||||
).unwrap())
|
||||
.expiry_time(Duration::from_secs(604800))
|
||||
.min_final_cltv_expiry(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())
|
||||
.private_route(RouteHint(vec![RouteHintHop {
|
||||
src_node_id: PublicKey::from_slice(&hex::decode(
|
||||
"03d06758583bb5154774a6eb221b1276c9e82d65bbaceca806d90e20c108f4b1c7"
|
||||
).unwrap()).unwrap(),
|
||||
short_channel_id: (589390 << 40) | (3312 << 16) | 1,
|
||||
fees: RoutingFees { base_msat: 1000, proportional_millionths: 2500 },
|
||||
cltv_expiry_delta: 40,
|
||||
htlc_maximum_msat: None, htlc_minimum_msat: None,
|
||||
}]))
|
||||
.build_raw()
|
||||
.unwrap()
|
||||
.sign(|_| {
|
||||
RecoverableSignature::from_compact(
|
||||
&hex::decode("1b1160cf6186b55722c1ac7ea502086baaccaabdc76b326e666b7f309d972b15069bfca11cd365304b36f48230cc12f3f13a017aab65f7c165a169df32282a58").unwrap(),
|
||||
RecoveryId::from_i32(1).unwrap()
|
||||
)
|
||||
}).unwrap(),
|
||||
false, // Same features as set in InvoiceBuilder
|
||||
false, // No unknown fields
|
||||
),
|
||||
(
|
||||
"lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q5sqqqqqqqqqqqqqqqqsgq2a25dxl5hrntdtn6zvydt7d66hyzsyhqs4wdynavys42xgl6sgx9c4g7me86a27t07mdtfry458rtjr0v92cnmswpsjscgt2vcse3sgpz3uapa".to_owned(),
|
||||
InvoiceBuilder::new(Currency::Bitcoin)
|
||||
.amount_milli_satoshis(2_500_000_000)
|
||||
.timestamp(UNIX_EPOCH + Duration::from_secs(1496314658))
|
||||
.payment_secret(PaymentSecret([0x11; 32]))
|
||||
.payment_hash(sha256::Hash::from_hex(
|
||||
"0001020304050607080900010203040506070809000102030405060708090102"
|
||||
).unwrap())
|
||||
.description("coffee beans".to_owned())
|
||||
.build_raw()
|
||||
.unwrap()
|
||||
.sign(|_| {
|
||||
RecoverableSignature::from_compact(
|
||||
&hex::decode("5755469bf4b8e6b6ae7a1308d5f9bad5c82812e0855cd24fac242aa323fa820c5c551ede4faeabcb7fb6d5a464ad0e35c86f615589ee0e0c250c216a662198c1").unwrap(),
|
||||
RecoveryId::from_i32(1).unwrap()
|
||||
)
|
||||
}).unwrap(),
|
||||
true, // Different features than set in InvoiceBuilder
|
||||
false, // No unknown fields
|
||||
),
|
||||
(
|
||||
"LNBC25M1PVJLUEZPP5QQQSYQCYQ5RQWZQFQQQSYQCYQ5RQWZQFQQQSYQCYQ5RQWZQFQYPQDQ5VDHKVEN9V5SXYETPDEESSP5ZYG3ZYG3ZYG3ZYG3ZYG3ZYG3ZYG3ZYG3ZYG3ZYG3ZYG3ZYG3ZYGS9Q5SQQQQQQQQQQQQQQQQSGQ2A25DXL5HRNTDTN6ZVYDT7D66HYZSYHQS4WDYNAVYS42XGL6SGX9C4G7ME86A27T07MDTFRY458RTJR0V92CNMSWPSJSCGT2VCSE3SGPZ3UAPA".to_owned(),
|
||||
InvoiceBuilder::new(Currency::Bitcoin)
|
||||
.amount_milli_satoshis(2_500_000_000)
|
||||
.timestamp(UNIX_EPOCH + Duration::from_secs(1496314658))
|
||||
.payment_secret(PaymentSecret([0x11; 32]))
|
||||
.payment_hash(sha256::Hash::from_hex(
|
||||
"0001020304050607080900010203040506070809000102030405060708090102"
|
||||
).unwrap())
|
||||
.description("coffee beans".to_owned())
|
||||
.build_raw()
|
||||
.unwrap()
|
||||
.sign(|_| {
|
||||
RecoverableSignature::from_compact(
|
||||
&hex::decode("5755469bf4b8e6b6ae7a1308d5f9bad5c82812e0855cd24fac242aa323fa820c5c551ede4faeabcb7fb6d5a464ad0e35c86f615589ee0e0c250c216a662198c1").unwrap(),
|
||||
RecoveryId::from_i32(1).unwrap()
|
||||
)
|
||||
}).unwrap(),
|
||||
true, // Different features than set in InvoiceBuilder
|
||||
false, // No unknown fields
|
||||
),
|
||||
(
|
||||
"lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q5sqqqqqqqqqqqqqqqqsgq2qrqqqfppnqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqppnqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpp4qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhpnqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhp4qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqspnqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsp4qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnp5qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnpkqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqz599y53s3ujmcfjp5xrdap68qxymkqphwsexhmhr8wdz5usdzkzrse33chw6dlp3jhuhge9ley7j2ayx36kawe7kmgg8sv5ugdyusdcqzn8z9x".to_owned(),
|
||||
InvoiceBuilder::new(Currency::Bitcoin)
|
||||
.amount_milli_satoshis(2_500_000_000)
|
||||
.timestamp(UNIX_EPOCH + Duration::from_secs(1496314658))
|
||||
.payment_secret(PaymentSecret([0x11; 32]))
|
||||
.payment_hash(sha256::Hash::from_hex(
|
||||
"0001020304050607080900010203040506070809000102030405060708090102"
|
||||
).unwrap())
|
||||
.description("coffee beans".to_owned())
|
||||
.build_raw()
|
||||
.unwrap()
|
||||
.sign(|_| {
|
||||
RecoverableSignature::from_compact(
|
||||
&hex::decode("150a5252308f25bc2641a186de87470189bb003774326beee33b9a2a720d1584386631c5dda6fc3195f97464bfc93d2574868eadd767d6da1078329c4349c837").unwrap(),
|
||||
RecoveryId::from_i32(0).unwrap()
|
||||
)
|
||||
}).unwrap(),
|
||||
true, // Different features than set in InvoiceBuilder
|
||||
true, // Some unknown fields
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn serialize() {
|
||||
for (serialized, deserialized, _) in get_test_tuples() {
|
||||
assert_eq!(deserialized.to_string(), serialized);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize() {
|
||||
for (serialized, deserialized, maybe_error) in get_test_tuples() {
|
||||
fn invoice_deserialize() {
|
||||
for (serialized, deserialized, ignore_feature_diff, ignore_unknown_fields) in get_test_tuples() {
|
||||
eprintln!("Testing invoice {}...", serialized);
|
||||
let parsed = serialized.parse::<SignedRawInvoice>().unwrap();
|
||||
|
||||
assert_eq!(parsed, deserialized);
|
||||
let (parsed_invoice, _, parsed_sig) = parsed.into_parts();
|
||||
let (deserialized_invoice, _, deserialized_sig) = deserialized.into_parts();
|
||||
|
||||
let validated = Invoice::from_signed(parsed);
|
||||
assert_eq!(deserialized_sig, parsed_sig);
|
||||
assert_eq!(deserialized_invoice.hrp, parsed_invoice.hrp);
|
||||
assert_eq!(deserialized_invoice.data.timestamp, parsed_invoice.data.timestamp);
|
||||
|
||||
if let Some(error) = maybe_error {
|
||||
assert_eq!(Err(error), validated);
|
||||
} else {
|
||||
assert!(validated.is_ok());
|
||||
let mut deserialized_hunks: HashSet<_> = deserialized_invoice.data.tagged_fields.iter().collect();
|
||||
let mut parsed_hunks: HashSet<_> = parsed_invoice.data.tagged_fields.iter().collect();
|
||||
if ignore_feature_diff {
|
||||
deserialized_hunks.retain(|h|
|
||||
if let RawTaggedField::KnownSemantics(TaggedField::Features(_)) = h { false } else { true });
|
||||
parsed_hunks.retain(|h|
|
||||
if let RawTaggedField::KnownSemantics(TaggedField::Features(_)) = h { false } else { true });
|
||||
}
|
||||
if ignore_unknown_fields {
|
||||
parsed_hunks.retain(|h|
|
||||
if let RawTaggedField::UnknownSemantics(_) = h { false } else { true });
|
||||
}
|
||||
assert_eq!(deserialized_hunks, parsed_hunks);
|
||||
|
||||
Invoice::from_signed(serialized.parse::<SignedRawInvoice>().unwrap()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bolt_invalid_invoices() {
|
||||
// Tests the BOLT 11 invalid invoice test vectors
|
||||
assert_eq!(Invoice::from_str(
|
||||
"lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q4psqqqqqqqqqqqqqqqqsgqtqyx5vggfcsll4wu246hz02kp85x4katwsk9639we5n5yngc3yhqkm35jnjw4len8vrnqnf5ejh0mzj9n3vz2px97evektfm2l6wqccp3y7372"
|
||||
), Err(ParseOrSemanticError::SemanticError(SemanticError::InvalidFeatures)));
|
||||
assert_eq!(Invoice::from_str(
|
||||
"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrnt"
|
||||
), Err(ParseOrSemanticError::ParseError(ParseError::Bech32Error(bech32::Error::InvalidChecksum))));
|
||||
assert_eq!(Invoice::from_str(
|
||||
"pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny"
|
||||
), Err(ParseOrSemanticError::ParseError(ParseError::Bech32Error(bech32::Error::MissingSeparator))));
|
||||
assert_eq!(Invoice::from_str(
|
||||
"LNBC2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny"
|
||||
), Err(ParseOrSemanticError::ParseError(ParseError::Bech32Error(bech32::Error::MixedCase))));
|
||||
assert_eq!(Invoice::from_str(
|
||||
"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqwgt7mcn5yqw3yx0w94pswkpq6j9uh6xfqqqtsk4tnarugeektd4hg5975x9am52rz4qskukxdmjemg92vvqz8nvmsye63r5ykel43pgz7zq0g2"
|
||||
), Err(ParseOrSemanticError::SemanticError(SemanticError::InvalidSignature)));
|
||||
assert_eq!(Invoice::from_str(
|
||||
"lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6na6hlh"
|
||||
), Err(ParseOrSemanticError::ParseError(ParseError::TooShortDataPart)));
|
||||
assert_eq!(Invoice::from_str(
|
||||
"lnbc2500x1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqrrzc4cvfue4zp3hggxp47ag7xnrlr8vgcmkjxk3j5jqethnumgkpqp23z9jclu3v0a7e0aruz366e9wqdykw6dxhdzcjjhldxq0w6wgqcnu43j"
|
||||
), Err(ParseOrSemanticError::ParseError(ParseError::UnknownSiPrefix)));
|
||||
assert_eq!(Invoice::from_str(
|
||||
"lnbc2500000001p1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgq0lzc236j96a95uv0m3umg28gclm5lqxtqqwk32uuk4k6673k6n5kfvx3d2h8s295fad45fdhmusm8sjudfhlf6dcsxmfvkeywmjdkxcp99202x"
|
||||
), Err(ParseOrSemanticError::SemanticError(SemanticError::ImpreciseAmount)));
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
use io;
|
||||
use prelude::*;
|
||||
use core::{cmp, fmt};
|
||||
use core::hash::{Hash, Hasher};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use bitcoin::bech32;
|
||||
|
@ -362,6 +363,11 @@ impl<T: sealed::Context> Clone for Features<T> {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl<T: sealed::Context> Hash for Features<T> {
|
||||
fn hash<H: Hasher>(&self, hasher: &mut H) {
|
||||
self.flags.hash(hasher);
|
||||
}
|
||||
}
|
||||
impl<T: sealed::Context> PartialEq for Features<T> {
|
||||
fn eq(&self, o: &Self) -> bool {
|
||||
self.flags.eq(&o.flags)
|
||||
|
@ -548,7 +554,9 @@ impl<T: sealed::Context> Features<T> {
|
|||
&self.flags
|
||||
}
|
||||
|
||||
pub(crate) fn requires_unknown_bits(&self) -> bool {
|
||||
/// Returns true if this `Features` object contains unknown feature flags which are set as
|
||||
/// "required".
|
||||
pub fn requires_unknown_bits(&self) -> bool {
|
||||
// Bitwise AND-ing with all even bits set except for known features will select required
|
||||
// unknown features.
|
||||
let byte_count = T::KNOWN_FEATURE_MASK.len();
|
||||
|
|
|
@ -533,7 +533,7 @@ impl_writeable_tlv_based!(ChannelInfo, {
|
|||
|
||||
|
||||
/// Fees for routing via a given channel or a node
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug, Hash)]
|
||||
pub struct RoutingFees {
|
||||
/// Flat routing fee in satoshis
|
||||
pub base_msat: u32,
|
||||
|
|
|
@ -28,7 +28,7 @@ use core::cmp;
|
|||
use core::ops::Deref;
|
||||
|
||||
/// A hop in a route
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||
pub struct RouteHop {
|
||||
/// The node_id of the node at this hop.
|
||||
pub pubkey: PublicKey,
|
||||
|
@ -60,7 +60,7 @@ impl_writeable_tlv_based!(RouteHop, {
|
|||
|
||||
/// A route directs a payment from the sender (us) to the recipient. If the recipient supports MPP,
|
||||
/// it can take multiple paths. Each path is composed of one or more hops through the network.
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||
pub struct Route {
|
||||
/// The list of routes taken for a single (potentially-)multi-part payment. The pubkey of the
|
||||
/// last RouteHop in each path must be the same.
|
||||
|
@ -108,11 +108,11 @@ impl Readable for Route {
|
|||
}
|
||||
|
||||
/// A list of hops along a payment path terminating with a channel to the recipient.
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct RouteHint(pub Vec<RouteHintHop>);
|
||||
|
||||
/// A channel descriptor for a hop along a payment path.
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct RouteHintHop {
|
||||
/// The node_id of the non-target end of the route
|
||||
pub src_node_id: PublicKey,
|
||||
|
|
Loading…
Add table
Reference in a new issue