Merge pull request #1055 from lightning-signer/2021-08-anchor-tx
authorMatt Corallo <649246+TheBlueMatt@users.noreply.github.com>
Thu, 2 Sep 2021 21:54:11 +0000 (21:54 +0000)
committerGitHub <noreply@github.com>
Thu, 2 Sep 2021 21:54:11 +0000 (21:54 +0000)
17 files changed:
.gitignore
fuzz/src/chanmon_consistency.rs
fuzz/src/full_stack.rs
lightning-invoice/Cargo.toml
lightning-invoice/src/de.rs
lightning-invoice/src/lib.rs
lightning-invoice/src/utils.rs
lightning-invoice/tests/ser_de.rs
lightning/src/chain/keysinterface.rs
lightning/src/ln/channel.rs
lightning/src/ln/features.rs
lightning/src/ln/functional_tests.rs
lightning/src/ln/msgs.rs
lightning/src/routing/network_graph.rs
lightning/src/routing/router.rs
lightning/src/util/enforcing_trait_impls.rs
lightning/src/util/test_utils.rs

index cfb551d133af550916793eb5689e82826e26329a..a108267c2fd1d725aa0e6d8bb1b8f0371b7eea51 100644 (file)
@@ -7,4 +7,4 @@ lightning-c-bindings/a.out
 **/*.rs.bk
 Cargo.lock
 .idea
-
+lightning/target
index 7eacf25365b0a45157f578ad210be31540dee052..88826d65570fb94c7cfda47de7cfa58c53e5027e 100644 (file)
@@ -41,7 +41,7 @@ use lightning::ln::channel::FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE;
 use lightning::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures};
 use lightning::ln::msgs::{CommitmentUpdate, ChannelMessageHandler, DecodeError, UpdateAddHTLC, Init};
 use lightning::ln::script::ShutdownScript;
-use lightning::util::enforcing_trait_impls::{EnforcingSigner, INITIAL_REVOKED_COMMITMENT_NUMBER};
+use lightning::util::enforcing_trait_impls::{EnforcingSigner, EnforcementState};
 use lightning::util::errors::APIError;
 use lightning::util::events;
 use lightning::util::logger::Logger;
@@ -161,7 +161,7 @@ impl chain::Watch<EnforcingSigner> for TestChainMonitor {
 struct KeyProvider {
        node_id: u8,
        rand_bytes_id: atomic::AtomicU32,
-       revoked_commitments: Mutex<HashMap<[u8;32], Arc<Mutex<u64>>>>,
+       enforcement_states: Mutex<HashMap<[u8;32], Arc<Mutex<EnforcementState>>>>,
 }
 impl KeysInterface for KeyProvider {
        type Signer = EnforcingSigner;
@@ -198,7 +198,7 @@ impl KeysInterface for KeyProvider {
                        channel_value_satoshis,
                        [0; 32],
                );
-               let revoked_commitment = self.make_revoked_commitment_cell(keys.commitment_seed);
+               let revoked_commitment = self.make_enforcement_state_cell(keys.commitment_seed);
                EnforcingSigner::new_with_revoked(keys, revoked_commitment, false)
        }
 
@@ -213,14 +213,11 @@ impl KeysInterface for KeyProvider {
                let mut reader = std::io::Cursor::new(buffer);
 
                let inner: InMemorySigner = Readable::read(&mut reader)?;
-               let revoked_commitment = self.make_revoked_commitment_cell(inner.commitment_seed);
-
-               let last_commitment_number = Readable::read(&mut reader)?;
+               let state = self.make_enforcement_state_cell(inner.commitment_seed);
 
                Ok(EnforcingSigner {
                        inner,
-                       last_commitment_number: Arc::new(Mutex::new(last_commitment_number)),
-                       revoked_commitment,
+                       state,
                        disable_revocation_policy_check: false,
                })
        }
@@ -231,10 +228,10 @@ impl KeysInterface for KeyProvider {
 }
 
 impl KeyProvider {
-       fn make_revoked_commitment_cell(&self, commitment_seed: [u8; 32]) -> Arc<Mutex<u64>> {
-               let mut revoked_commitments = self.revoked_commitments.lock().unwrap();
+       fn make_enforcement_state_cell(&self, commitment_seed: [u8; 32]) -> Arc<Mutex<EnforcementState>> {
+               let mut revoked_commitments = self.enforcement_states.lock().unwrap();
                if !revoked_commitments.contains_key(&commitment_seed) {
-                       revoked_commitments.insert(commitment_seed, Arc::new(Mutex::new(INITIAL_REVOKED_COMMITMENT_NUMBER)));
+                       revoked_commitments.insert(commitment_seed, Arc::new(Mutex::new(EnforcementState::new())));
                }
                let cell = revoked_commitments.get(&commitment_seed).unwrap();
                Arc::clone(cell)
@@ -351,7 +348,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
        macro_rules! make_node {
                ($node_id: expr, $fee_estimator: expr) => { {
                        let logger: Arc<dyn Logger> = Arc::new(test_logger::TestLogger::new($node_id.to_string(), out.clone()));
-                       let keys_manager = Arc::new(KeyProvider { node_id: $node_id, rand_bytes_id: atomic::AtomicU32::new(0), revoked_commitments: Mutex::new(HashMap::new()) });
+                       let keys_manager = Arc::new(KeyProvider { node_id: $node_id, rand_bytes_id: atomic::AtomicU32::new(0), enforcement_states: Mutex::new(HashMap::new()) });
                        let monitor = Arc::new(TestChainMonitor::new(broadcast.clone(), logger.clone(), $fee_estimator.clone(), Arc::new(TestPersister{}), Arc::clone(&keys_manager)));
 
                        let mut config = UserConfig::default();
index 510966f0d4a63dd1c33815073a1f07b42548f56f..28592ffda512fe6677a6431ebf35303526b2a648 100644 (file)
@@ -42,7 +42,7 @@ use lightning::routing::network_graph::NetGraphMsgHandler;
 use lightning::util::config::UserConfig;
 use lightning::util::errors::APIError;
 use lightning::util::events::Event;
-use lightning::util::enforcing_trait_impls::EnforcingSigner;
+use lightning::util::enforcing_trait_impls::{EnforcingSigner, EnforcementState};
 use lightning::util::logger::Logger;
 use lightning::util::ser::Readable;
 
@@ -315,8 +315,15 @@ impl KeysInterface for KeyProvider {
                (ctr >> 8*7) as u8, (ctr >> 8*6) as u8, (ctr >> 8*5) as u8, (ctr >> 8*4) as u8, (ctr >> 8*3) as u8, (ctr >> 8*2) as u8, (ctr >> 8*1) as u8, 14, (ctr >> 8*0) as u8]
        }
 
-       fn read_chan_signer(&self, data: &[u8]) -> Result<EnforcingSigner, DecodeError> {
-               EnforcingSigner::read(&mut std::io::Cursor::new(data))
+       fn read_chan_signer(&self, mut data: &[u8]) -> Result<EnforcingSigner, DecodeError> {
+               let inner: InMemorySigner = Readable::read(&mut data)?;
+               let state = Arc::new(Mutex::new(EnforcementState::new()));
+
+               Ok(EnforcingSigner::new_with_revoked(
+                       inner,
+                       state,
+                       false
+               ))
        }
 
        fn sign_invoice(&self, _invoice_preimage: Vec<u8>) -> Result<RecoverableSignature, ()> {
index 8c6623b8fa5a0c17148210e6491eee1f5dacc20c..baa9a79c5d5af3a055fdbc1d7894dccb47338e32 100644 (file)
@@ -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"] }
index dbcb74e073aeaec450a3333159cfb6ace5232115..777ac660f8e6b764d6ac5a191af3bcbb3636bc1d 100644 (file)
@@ -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)}
index 2ce58f296f9b26b06ec4c92d8b634a57f680d5b3..61e394da6dba4ef70979a5dae1d673ce2b483732 100644 (file)
@@ -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(())
        }
 
-       /// 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."
+       /// 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 {
+               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> {
+               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()
index f419f5f7f24077aae76d6048d0935aac50cf3d6f..df2bbfd8f12459381d3296dc44499cffa2030d07 100644 (file)
@@ -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();
index 442166e740e7568f0a25b24883c1f3ad0e016f2e..a2cc0e2e2b62f70aa883946e45fcc123a510dc9e 100644 (file)
@@ -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(),
+                       false, // Same features as set in InvoiceBuilder
+                       false, // No unknown fields
+               ),
+               (
+                       "lnbc2500u1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpu9qrsgqhtjpauu9ur7fw2thcl4y9vfvh4m9wlfyz2gem29g5ghe2aak2pm3ps8fdhtceqsaagty2vph7utlgj48u0ged6a337aewvraedendscp573dxr".to_owned(),
+                       InvoiceBuilder::new(Currency::Bitcoin)
+                               .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("ナンセンス 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(),
-                       None
+                       false, // Same features as set in InvoiceBuilder
+                       false, // No unknown fields
                ),
                (
-                       "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qq\
-                       dhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqscc6gd6ql3jrc5yzme8v4ntcewwz5cnw92tz0pc8qcuufvq7k\
-                       hhr8wpald05e92xw006sq94mg8v2ndf4sefvf9sygkshp5zfem29trqq2yxxz7".to_owned(),
+                       "lnbc20m1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qrsgq7ea976txfraylvgzuxs8kgcw23ezlrszfnh8r6qtfpr6cxga50aj6txm9rxrydzd06dfeawfk6swupvz4erwnyutnjq7x39ymw6j38gp7ynn44".to_owned(),
                        InvoiceBuilder::new(Currency::Bitcoin)
-                               .amount_pico_btc(20000000000)
+                               .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_hash(sha256::Hash::from_hex(
-                                       "3925b6f67e2c340036ed12093dd44e0368df1b6ea26c53dbe4811f58fd5db8c1"
+                               .build_raw()
+                               .unwrap()
+                               .sign(|_| {
+                                       RecoverableSignature::from_compact(
+                                               &hex::decode("f67a5f696648fa4fb102e1a07b230e54722f8e024cee71e80b4847ac191da3fb2d2cdb28cc32344d7e9a9cf5c9b6a0ee0582ae46e9938b9c81e344a4dbb5289d").unwrap(),
+                                               RecoveryId::from_i32(1).unwrap()
+                                       )
+                               }).unwrap(),
+                       false, // Same features as set in InvoiceBuilder
+                       false, // No unknown fields
+               ),
+               (
+                       "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())
+                               .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(|_| {
+                                       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(
-                                               & [
-                                                       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
-                                               ],
+                                               &hex::decode("6a6586db4e8f6d40e3a5bb92e4df5110c627e9ce493af237e20a046b4e86ea200178c59564ecf892f33a9558bf041b6ad2cb8292d7a6c351fbb7f2ae2d16b54e").unwrap(),
                                                RecoveryId::from_i32(0).unwrap()
                                        )
                                }).unwrap(),
-                       None
+                       false, // Same features as set in InvoiceBuilder
+                       false, // No unknown fields
                ),
                (
-                       "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp59g4z52329g4z52329g4z52329g4z52329g4z52329g4z52329g4q9qrsgqzfhag3vsafx4e5qssalvw4rn0phsvpp3e5h2xxyk9l8fxsutvndx9t840dqvdrlu2gqmk0q8apqrgnjy9amc07hmjl9e9yzqjks5w2gqgjnyms".to_owned(),
+                       "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())
-                               .description("coffee beans".to_string())
-                               .amount_pico_btc(20000000000)
+                               .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))
-                               .payment_secret(PaymentSecret([42; 32]))
+                               .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]
+                               })
                                .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("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]
                                })
-                               .unwrap(),
-                       None
-               )
+                               .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)));
+}
index c2e78a79394ea84cdbc69e88e269b863a614b948..44a03d09f8e717019c19c070159a7b7556b24723 100644 (file)
@@ -212,6 +212,13 @@ pub trait BaseSign {
        /// Note that the commitment number starts at (1 << 48) - 1 and counts backwards.
        // TODO: return a Result so we can signal a validation error
        fn release_commitment_secret(&self, idx: u64) -> [u8; 32];
+       /// Validate the counterparty's signatures on the holder commitment transaction and HTLCs.
+       ///
+       /// This is required in order for the signer to make sure that releasing a commitment
+       /// secret won't leave us without a broadcastable holder transaction.
+       /// Policy checks should be implemented in this function, including checking the amount
+       /// sent to us and checking the HTLCs.
+       fn validate_holder_commitment(&self, holder_tx: &HolderCommitmentTransaction) -> Result<(), ()>;
        /// Gets the holder's channel public keys and basepoints
        fn pubkeys(&self) -> &ChannelPublicKeys;
        /// Gets an arbitrary identifier describing the set of keys which are provided back to you in
@@ -222,9 +229,17 @@ pub trait BaseSign {
        /// Create a signature for a counterparty's commitment transaction and associated HTLC transactions.
        ///
        /// Note that if signing fails or is rejected, the channel will be force-closed.
+       ///
+       /// Policy checks should be implemented in this function, including checking the amount
+       /// sent to us and checking the HTLCs.
        //
        // TODO: Document the things someone using this interface should enforce before signing.
        fn sign_counterparty_commitment(&self, commitment_tx: &CommitmentTransaction, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<(Signature, Vec<Signature>), ()>;
+       /// Validate the counterparty's revocation.
+       ///
+       /// This is required in order for the signer to make sure that the state has moved
+       /// forward and it is safe to sign the next counterparty commitment.
+       fn validate_counterparty_revocation(&self, idx: u64, secret: &SecretKey) -> Result<(), ()>;
 
        /// Create a signatures for a holder's commitment transaction and its claiming HTLC transactions.
        /// This will only ever be called with a non-revoked commitment_tx.  This will be called with the
@@ -558,6 +573,10 @@ impl BaseSign for InMemorySigner {
                chan_utils::build_commitment_secret(&self.commitment_seed, idx)
        }
 
+       fn validate_holder_commitment(&self, _holder_tx: &HolderCommitmentTransaction) -> Result<(), ()> {
+               Ok(())
+       }
+
        fn pubkeys(&self) -> &ChannelPublicKeys { &self.holder_channel_pubkeys }
        fn channel_keys_id(&self) -> [u8; 32] { self.channel_keys_id }
 
@@ -584,6 +603,10 @@ impl BaseSign for InMemorySigner {
                Ok((commitment_sig, htlc_sigs))
        }
 
+       fn validate_counterparty_revocation(&self, _idx: u64, _secret: &SecretKey) -> Result<(), ()> {
+               Ok(())
+       }
+
        fn sign_holder_commitment_and_htlcs(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<(Signature, Vec<Signature>), ()> {
                let funding_pubkey = PublicKey::from_secret_key(secp_ctx, &self.funding_key);
                let funding_redeemscript = make_funding_redeemscript(&funding_pubkey, &self.counterparty_pubkeys().funding_pubkey);
index b44a642b7f265226f9a83fd8ca7f0f7e21cd0029..0481a93fcd97e6fc566436dbbad9fad95f3d871c 100644 (file)
@@ -1801,6 +1801,9 @@ impl<Signer: Sign> Channel<Signer> {
                        self.counterparty_funding_pubkey()
                );
 
+               self.holder_signer.validate_holder_commitment(&holder_commitment_tx)
+                       .map_err(|_| ChannelError::Close("Failed to validate our commitment".to_owned()))?;
+
                // Now that we're past error-generating stuff, update our local state:
 
                let funding_redeemscript = self.get_funding_redeemscript();
@@ -1875,6 +1878,9 @@ impl<Signer: Sign> Channel<Signer> {
                        self.counterparty_funding_pubkey()
                );
 
+               self.holder_signer.validate_holder_commitment(&holder_commitment_tx)
+                       .map_err(|_| ChannelError::Close("Failed to validate our commitment".to_owned()))?;
+
 
                let funding_redeemscript = self.get_funding_redeemscript();
                let funding_txo = self.get_funding_txo().unwrap();
@@ -2512,6 +2518,8 @@ impl<Signer: Sign> Channel<Signer> {
                );
 
                let next_per_commitment_point = self.holder_signer.get_per_commitment_point(self.cur_holder_commitment_transaction_number - 1, &self.secp_ctx);
+               self.holder_signer.validate_holder_commitment(&holder_commitment_tx)
+                       .map_err(|_| (None, ChannelError::Close("Failed to validate our commitment".to_owned())))?;
                let per_commitment_secret = self.holder_signer.release_commitment_secret(self.cur_holder_commitment_transaction_number + 1);
 
                // Update state now that we've passed all the can-fail calls...
@@ -2748,8 +2756,10 @@ impl<Signer: Sign> Channel<Signer> {
                        return Err(ChannelError::Close("Peer sent revoke_and_ack after we'd started exchanging closing_signeds".to_owned()));
                }
 
+               let secret = secp_check!(SecretKey::from_slice(&msg.per_commitment_secret), "Peer provided an invalid per_commitment_secret".to_owned());
+
                if let Some(counterparty_prev_commitment_point) = self.counterparty_prev_commitment_point {
-                       if PublicKey::from_secret_key(&self.secp_ctx, &secp_check!(SecretKey::from_slice(&msg.per_commitment_secret), "Peer provided an invalid per_commitment_secret".to_owned())) != counterparty_prev_commitment_point {
+                       if PublicKey::from_secret_key(&self.secp_ctx, &secret) != counterparty_prev_commitment_point {
                                return Err(ChannelError::Close("Got a revoke commitment secret which didn't correspond to their current pubkey".to_owned()));
                        }
                }
@@ -2771,6 +2781,11 @@ impl<Signer: Sign> Channel<Signer> {
                        *self.next_remote_commitment_tx_fee_info_cached.lock().unwrap() = None;
                }
 
+               self.holder_signer.validate_counterparty_revocation(
+                       self.cur_counterparty_commitment_transaction_number + 1,
+                       &secret
+               ).map_err(|_| ChannelError::Close("Failed to validate revocation from peer".to_owned()))?;
+
                self.commitment_secrets.provide_secret(self.cur_counterparty_commitment_transaction_number + 1, msg.per_commitment_secret)
                        .map_err(|_| ChannelError::Close("Previous secrets did not match new one".to_owned()))?;
                self.latest_monitor_update_id += 1;
index e78fa3d50d2d05c5bfcafe1fd58eb1b654c167f7..d1f6b89db4f87e968e3624289331c00c476875fe 100644 (file)
@@ -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();
index b7eb6c006f4baddbdc0b71c18d3f19704cc0c457..06cc80cc18b0a5b770be83ce25cbb82535d932ba 100644 (file)
@@ -1301,6 +1301,9 @@ fn test_fee_spike_violation_fails_htlc() {
                let chan_lock = nodes[0].node.channel_state.lock().unwrap();
                let local_chan = chan_lock.by_id.get(&chan.2).unwrap();
                let chan_signer = local_chan.get_signer();
+               // Make the signer believe we validated another commitment, so we can release the secret
+               chan_signer.get_enforcement_state().last_holder_commitment -= 1;
+
                let pubkeys = chan_signer.pubkeys();
                (pubkeys.revocation_basepoint, pubkeys.htlc_basepoint,
                 chan_signer.release_commitment_secret(INITIAL_COMMITMENT_NUMBER),
@@ -7984,7 +7987,7 @@ fn test_counterparty_raa_skip_no_crash() {
        // commitment transaction, we would have happily carried on and provided them the next
        // commitment transaction based on one RAA forward. This would probably eventually have led to
        // channel closure, but it would not have resulted in funds loss. Still, our
-       // EnforcingSigner would have paniced as it doesn't like jumps into the future. Here, we
+       // EnforcingSigner would have panicked as it doesn't like jumps into the future. Here, we
        // check simply that the channel is closed in response to such an RAA, but don't check whether
        // we decide to punish our counterparty for revoking their funds (as we don't currently
        // implement that).
@@ -7995,11 +7998,19 @@ fn test_counterparty_raa_skip_no_crash() {
        let channel_id = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known()).2;
 
        let mut guard = nodes[0].node.channel_state.lock().unwrap();
-       let keys = &guard.by_id.get_mut(&channel_id).unwrap().get_signer();
+       let keys = guard.by_id.get_mut(&channel_id).unwrap().get_signer();
+
        const INITIAL_COMMITMENT_NUMBER: u64 = (1 << 48) - 1;
+
+       // Make signer believe we got a counterparty signature, so that it allows the revocation
+       keys.get_enforcement_state().last_holder_commitment -= 1;
        let per_commitment_secret = keys.release_commitment_secret(INITIAL_COMMITMENT_NUMBER);
+
        // Must revoke without gaps
+       keys.get_enforcement_state().last_holder_commitment -= 1;
        keys.release_commitment_secret(INITIAL_COMMITMENT_NUMBER - 1);
+
+       keys.get_enforcement_state().last_holder_commitment -= 1;
        let next_per_commitment_point = PublicKey::from_secret_key(&Secp256k1::new(),
                &SecretKey::from_slice(&keys.release_commitment_secret(INITIAL_COMMITMENT_NUMBER - 2)).unwrap());
 
index f94909a9ae5499c35a5c6f5719c1705ede98ed91..5b49f43b1188013c5f44822e59622409590aefb3 100644 (file)
@@ -194,7 +194,7 @@ pub struct FundingCreated {
        pub funding_txid: Txid,
        /// The specific output index funding this channel
        pub funding_output_index: u16,
-       /// The signature of the channel initiator (funder) on the funding transaction
+       /// The signature of the channel initiator (funder) on the initial commitment transaction
        pub signature: Signature,
 }
 
@@ -203,7 +203,7 @@ pub struct FundingCreated {
 pub struct FundingSigned {
        /// The channel ID
        pub channel_id: [u8; 32],
-       /// The signature of the channel acceptor (fundee) on the funding transaction
+       /// The signature of the channel acceptor (fundee) on the initial commitment transaction
        pub signature: Signature,
 }
 
index 486b71578f3fde8775e1d7fe86bd0283c5ef7d8e..8accdab60882321dea801497643478b702a21b47 100644 (file)
@@ -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,
index 13356cc1c74334193fed69be42a05b718d2ab89d..5030f6aaacbf9569a0ff5e2d26a20a31104c0c9b 100644 (file)
@@ -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,
@@ -375,10 +375,11 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
                return Err(LightningError{err: "Cannot send a payment of 0 msat".to_owned(), action: ErrorAction::IgnoreError});
        }
 
-       let last_hops = last_hops.iter().filter_map(|hops| hops.0.last()).collect::<Vec<_>>();
-       for last_hop in last_hops.iter() {
-               if last_hop.src_node_id == *payee {
-                       return Err(LightningError{err: "Last hop cannot have a payee as a source.".to_owned(), action: ErrorAction::IgnoreError});
+       for route in last_hops.iter() {
+               for hop in &route.0 {
+                       if hop.src_node_id == *payee {
+                               return Err(LightningError{err: "Last hop cannot have a payee as a source.".to_owned(), action: ErrorAction::IgnoreError});
+                       }
                }
        }
 
@@ -876,39 +877,89 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
                // If a caller provided us with last hops, add them to routing targets. Since this happens
                // earlier than general path finding, they will be somewhat prioritized, although currently
                // it matters only if the fees are exactly the same.
-               for hop in last_hops.iter() {
+               for route in last_hops.iter().filter(|route| !route.0.is_empty()) {
+                       let first_hop_in_route = &(route.0)[0];
                        let have_hop_src_in_graph =
-                               // Only add the last hop to our candidate set if either we have a direct channel or
-                               // they are in the regular network graph.
-                               first_hop_targets.get(&hop.src_node_id).is_some() ||
-                               network.get_nodes().get(&hop.src_node_id).is_some();
+                               // Only add the hops in this route to our candidate set if either
+                               // we have a direct channel to the first hop or the first hop is
+                               // in the regular network graph.
+                               first_hop_targets.get(&first_hop_in_route.src_node_id).is_some() ||
+                               network.get_nodes().get(&first_hop_in_route.src_node_id).is_some();
                        if have_hop_src_in_graph {
-                               // BOLT 11 doesn't allow inclusion of features for the last hop hints, which
-                               // really sucks, cause we're gonna need that eventually.
-                               let last_hop_htlc_minimum_msat: u64 = match hop.htlc_minimum_msat {
-                                       Some(htlc_minimum_msat) => htlc_minimum_msat,
-                                       None => 0
-                               };
-                               let directional_info = DummyDirectionalChannelInfo {
-                                       cltv_expiry_delta: hop.cltv_expiry_delta as u32,
-                                       htlc_minimum_msat: last_hop_htlc_minimum_msat,
-                                       htlc_maximum_msat: hop.htlc_maximum_msat,
-                                       fees: hop.fees,
-                               };
-                               // We assume that the recipient only included route hints for routes which had
-                               // sufficient value to route `final_value_msat`. Note that in the case of "0-value"
-                               // invoices where the invoice does not specify value this may not be the case, but
-                               // better to include the hints than not.
-                               if add_entry!(hop.short_channel_id, hop.src_node_id, payee, directional_info, Some((final_value_msat + 999) / 1000), &empty_channel_features, 0, path_value_msat, 0) {
-                                       // If this hop connects to a node with which we have a direct channel,
-                                       // ignore the network graph and, if the last hop was added, add our
-                                       // direct channel to the candidate set.
-                                       //
-                                       // Note that we *must* check if the last hop was added as `add_entry`
-                                       // always assumes that the third argument is a node to which we have a
-                                       // path.
-                                       if let Some(&(ref first_hop, ref features, ref outbound_capacity_msat, _)) = first_hop_targets.get(&hop.src_node_id) {
-                                               add_entry!(first_hop, *our_node_id , hop.src_node_id, dummy_directional_info, Some(outbound_capacity_msat / 1000), features, 0, path_value_msat, 0);
+                               // We start building the path from reverse, i.e., from payee
+                               // to the first RouteHintHop in the path.
+                               let hop_iter = route.0.iter().rev();
+                               let prev_hop_iter = core::iter::once(payee).chain(
+                                       route.0.iter().skip(1).rev().map(|hop| &hop.src_node_id));
+                               let mut hop_used = true;
+                               let mut aggregate_next_hops_fee_msat: u64 = 0;
+                               let mut aggregate_next_hops_path_htlc_minimum_msat: u64 = 0;
+
+                               for (idx, (hop, prev_hop_id)) in hop_iter.zip(prev_hop_iter).enumerate() {
+                                       // BOLT 11 doesn't allow inclusion of features for the last hop hints, which
+                                       // really sucks, cause we're gonna need that eventually.
+                                       let hop_htlc_minimum_msat: u64 = hop.htlc_minimum_msat.unwrap_or(0);
+
+                                       let directional_info = DummyDirectionalChannelInfo {
+                                               cltv_expiry_delta: hop.cltv_expiry_delta as u32,
+                                               htlc_minimum_msat: hop_htlc_minimum_msat,
+                                               htlc_maximum_msat: hop.htlc_maximum_msat,
+                                               fees: hop.fees,
+                                       };
+
+                                       let reqd_channel_cap = if let Some (val) = final_value_msat.checked_add(match idx {
+                                               0 => 999,
+                                               _ => aggregate_next_hops_fee_msat.checked_add(999).unwrap_or(u64::max_value())
+                                       }) { Some( val / 1000 ) } else { break; }; // converting from msat or breaking if max ~ infinity
+
+
+                                       // We assume that the recipient only included route hints for routes which had
+                                       // sufficient value to route `final_value_msat`. Note that in the case of "0-value"
+                                       // invoices where the invoice does not specify value this may not be the case, but
+                                       // better to include the hints than not.
+                                       if !add_entry!(hop.short_channel_id, hop.src_node_id, prev_hop_id, directional_info, reqd_channel_cap, &empty_channel_features, aggregate_next_hops_fee_msat, path_value_msat, aggregate_next_hops_path_htlc_minimum_msat) {
+                                               // If this hop was not used then there is no use checking the preceding hops
+                                               // in the RouteHint. We can break by just searching for a direct channel between
+                                               // last checked hop and first_hop_targets
+                                               hop_used = false;
+                                       }
+
+                                       // Searching for a direct channel between last checked hop and first_hop_targets
+                                       if let Some(&(ref first_hop, ref features, ref outbound_capacity_msat, _)) = first_hop_targets.get(&prev_hop_id) {
+                                               add_entry!(first_hop, *our_node_id , prev_hop_id, dummy_directional_info, Some(outbound_capacity_msat / 1000), features, aggregate_next_hops_fee_msat, path_value_msat, aggregate_next_hops_path_htlc_minimum_msat);
+                                       }
+
+                                       if !hop_used {
+                                               break;
+                                       }
+
+                                       // In the next values of the iterator, the aggregate fees already reflects
+                                       // the sum of value sent from payer (final_value_msat) and routing fees
+                                       // for the last node in the RouteHint. We need to just add the fees to
+                                       // route through the current node so that the preceeding node (next iteration)
+                                       // can use it.
+                                       let hops_fee = compute_fees(aggregate_next_hops_fee_msat + final_value_msat, hop.fees)
+                                               .map_or(None, |inc| inc.checked_add(aggregate_next_hops_fee_msat));
+                                       aggregate_next_hops_fee_msat = if let Some(val) = hops_fee { val } else { break; };
+
+                                       let hop_htlc_minimum_msat_inc = if let Some(val) = compute_fees(aggregate_next_hops_path_htlc_minimum_msat, hop.fees) { val } else { break; };
+                                       let hops_path_htlc_minimum = aggregate_next_hops_path_htlc_minimum_msat
+                                               .checked_add(hop_htlc_minimum_msat_inc);
+                                       aggregate_next_hops_path_htlc_minimum_msat = if let Some(val) = hops_path_htlc_minimum { cmp::max(hop_htlc_minimum_msat, val) } else { break; };
+
+                                       if idx == route.0.len() - 1 {
+                                               // The last hop in this iterator is the first hop in
+                                               // overall RouteHint.
+                                               // If this hop connects to a node with which we have a direct channel,
+                                               // ignore the network graph and, if the last hop was added, add our
+                                               // direct channel to the candidate set.
+                                               //
+                                               // Note that we *must* check if the last hop was added as `add_entry`
+                                               // always assumes that the third argument is a node to which we have a
+                                               // path.
+                                               if let Some(&(ref first_hop, ref features, ref outbound_capacity_msat, _)) = first_hop_targets.get(&hop.src_node_id) {
+                                                       add_entry!(first_hop, *our_node_id , hop.src_node_id, dummy_directional_info, Some(outbound_capacity_msat / 1000), features, aggregate_next_hops_fee_msat, path_value_msat, aggregate_next_hops_path_htlc_minimum_msat);
+                                               }
                                        }
                                }
                        }
@@ -1343,7 +1394,7 @@ mod tests {
                let logger = Arc::new(test_utils::TestLogger::new());
                let chain_monitor = Arc::new(test_utils::TestChainSource::new(Network::Testnet));
                let net_graph_msg_handler = NetGraphMsgHandler::new(genesis_block(Network::Testnet).header.block_hash(), None, Arc::clone(&logger));
-               // Build network from our_id to node7:
+               // Build network from our_id to node6:
                //
                //        -1(1)2-  node0  -1(3)2-
                //       /                       \
@@ -1379,6 +1430,8 @@ mod tests {
                //      \                      /
                //       -1(7)2- node5 -1(10)2-
                //
+               // Channels 5, 8, 9 and 10 are private channels.
+               //
                // chan5  1-to-2: enabled, 100 msat fee
                // chan5  2-to-1: enabled, 0 fee
                //
@@ -2105,7 +2158,51 @@ mod tests {
                        cltv_expiry_delta: (8 << 8) | 1,
                        htlc_minimum_msat: None,
                        htlc_maximum_msat: None,
+               }
+               ]), RouteHint(vec![RouteHintHop {
+                       src_node_id: nodes[4].clone(),
+                       short_channel_id: 9,
+                       fees: RoutingFees {
+                               base_msat: 1001,
+                               proportional_millionths: 0,
+                       },
+                       cltv_expiry_delta: (9 << 8) | 1,
+                       htlc_minimum_msat: None,
+                       htlc_maximum_msat: None,
                }]), RouteHint(vec![RouteHintHop {
+                       src_node_id: nodes[5].clone(),
+                       short_channel_id: 10,
+                       fees: zero_fees,
+                       cltv_expiry_delta: (10 << 8) | 1,
+                       htlc_minimum_msat: None,
+                       htlc_maximum_msat: None,
+               }])]
+       }
+
+       fn last_hops_multi_private_channels(nodes: &Vec<PublicKey>) -> Vec<RouteHint> {
+               let zero_fees = RoutingFees {
+                       base_msat: 0,
+                       proportional_millionths: 0,
+               };
+               vec![RouteHint(vec![RouteHintHop {
+                       src_node_id: nodes[2].clone(),
+                       short_channel_id: 5,
+                       fees: RoutingFees {
+                               base_msat: 100,
+                               proportional_millionths: 0,
+                       },
+                       cltv_expiry_delta: (5 << 8) | 1,
+                       htlc_minimum_msat: None,
+                       htlc_maximum_msat: None,
+               }, RouteHintHop {
+                       src_node_id: nodes[3].clone(),
+                       short_channel_id: 8,
+                       fees: zero_fees,
+                       cltv_expiry_delta: (8 << 8) | 1,
+                       htlc_minimum_msat: None,
+                       htlc_maximum_msat: None,
+               }
+               ]), RouteHint(vec![RouteHintHop {
                        src_node_id: nodes[4].clone(),
                        short_channel_id: 9,
                        fees: RoutingFees {
@@ -2126,11 +2223,13 @@ mod tests {
        }
 
        #[test]
-       fn last_hops_test() {
+       fn partial_route_hint_test() {
                let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
                let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
 
                // Simple test across 2, 3, 5, and 4 via a last_hop channel
+               // Tests the behaviour when the RouteHint contains a suboptimal hop.
+               // RouteHint may be partially used by the algo to build the best path.
 
                // First check that last hop can't have its source as the payee.
                let invalid_last_hop = RouteHint(vec![RouteHintHop {
@@ -2145,7 +2244,7 @@ mod tests {
                        htlc_maximum_msat: None,
                }]);
 
-               let mut invalid_last_hops = last_hops(&nodes);
+               let mut invalid_last_hops = last_hops_multi_private_channels(&nodes);
                invalid_last_hops.push(invalid_last_hop);
                {
                        if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &net_graph_msg_handler.network_graph.read().unwrap(), &nodes[6], None, None, &invalid_last_hops.iter().collect::<Vec<_>>(), 100, 42, Arc::clone(&logger)) {
@@ -2153,7 +2252,79 @@ mod tests {
                        } else { panic!(); }
                }
 
-               let route = get_route(&our_id, &net_graph_msg_handler.network_graph.read().unwrap(), &nodes[6], None, None, &last_hops(&nodes).iter().collect::<Vec<_>>(), 100, 42, Arc::clone(&logger)).unwrap();
+               let route = get_route(&our_id, &net_graph_msg_handler.network_graph.read().unwrap(), &nodes[6], None, None, &last_hops_multi_private_channels(&nodes).iter().collect::<Vec<_>>(), 100, 42, Arc::clone(&logger)).unwrap();
+               assert_eq!(route.paths[0].len(), 5);
+
+               assert_eq!(route.paths[0][0].pubkey, nodes[1]);
+               assert_eq!(route.paths[0][0].short_channel_id, 2);
+               assert_eq!(route.paths[0][0].fee_msat, 100);
+               assert_eq!(route.paths[0][0].cltv_expiry_delta, (4 << 8) | 1);
+               assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags(2));
+               assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags(2));
+
+               assert_eq!(route.paths[0][1].pubkey, nodes[2]);
+               assert_eq!(route.paths[0][1].short_channel_id, 4);
+               assert_eq!(route.paths[0][1].fee_msat, 0);
+               assert_eq!(route.paths[0][1].cltv_expiry_delta, (6 << 8) | 1);
+               assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags(3));
+               assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags(4));
+
+               assert_eq!(route.paths[0][2].pubkey, nodes[4]);
+               assert_eq!(route.paths[0][2].short_channel_id, 6);
+               assert_eq!(route.paths[0][2].fee_msat, 0);
+               assert_eq!(route.paths[0][2].cltv_expiry_delta, (11 << 8) | 1);
+               assert_eq!(route.paths[0][2].node_features.le_flags(), &id_to_feature_flags(5));
+               assert_eq!(route.paths[0][2].channel_features.le_flags(), &id_to_feature_flags(6));
+
+               assert_eq!(route.paths[0][3].pubkey, nodes[3]);
+               assert_eq!(route.paths[0][3].short_channel_id, 11);
+               assert_eq!(route.paths[0][3].fee_msat, 0);
+               assert_eq!(route.paths[0][3].cltv_expiry_delta, (8 << 8) | 1);
+               // If we have a peer in the node map, we'll use their features here since we don't have
+               // a way of figuring out their features from the invoice:
+               assert_eq!(route.paths[0][3].node_features.le_flags(), &id_to_feature_flags(4));
+               assert_eq!(route.paths[0][3].channel_features.le_flags(), &id_to_feature_flags(11));
+
+               assert_eq!(route.paths[0][4].pubkey, nodes[6]);
+               assert_eq!(route.paths[0][4].short_channel_id, 8);
+               assert_eq!(route.paths[0][4].fee_msat, 100);
+               assert_eq!(route.paths[0][4].cltv_expiry_delta, 42);
+               assert_eq!(route.paths[0][4].node_features.le_flags(), &Vec::<u8>::new()); // We dont pass flags in from invoices yet
+               assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
+       }
+
+       fn empty_last_hop(nodes: &Vec<PublicKey>) -> Vec<RouteHint> {
+               let zero_fees = RoutingFees {
+                       base_msat: 0,
+                       proportional_millionths: 0,
+               };
+               vec![RouteHint(vec![RouteHintHop {
+                       src_node_id: nodes[3].clone(),
+                       short_channel_id: 8,
+                       fees: zero_fees,
+                       cltv_expiry_delta: (8 << 8) | 1,
+                       htlc_minimum_msat: None,
+                       htlc_maximum_msat: None,
+               }]), RouteHint(vec![
+
+               ]), RouteHint(vec![RouteHintHop {
+                       src_node_id: nodes[5].clone(),
+                       short_channel_id: 10,
+                       fees: zero_fees,
+                       cltv_expiry_delta: (10 << 8) | 1,
+                       htlc_minimum_msat: None,
+                       htlc_maximum_msat: None,
+               }])]
+       }
+
+       #[test]
+       fn ignores_empty_last_hops_test() {
+               let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
+               let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
+
+               // Test handling of an empty RouteHint passed in Invoice.
+
+               let route = get_route(&our_id, &net_graph_msg_handler.network_graph.read().unwrap(), &nodes[6], None, None, &empty_last_hop(&nodes).iter().collect::<Vec<_>>(), 100, 42, Arc::clone(&logger)).unwrap();
                assert_eq!(route.paths[0].len(), 5);
 
                assert_eq!(route.paths[0][0].pubkey, nodes[1]);
@@ -2194,6 +2365,190 @@ mod tests {
                assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
        }
 
+       fn multi_hint_last_hops(nodes: &Vec<PublicKey>) -> Vec<RouteHint> {
+               let zero_fees = RoutingFees {
+                       base_msat: 0,
+                       proportional_millionths: 0,
+               };
+               vec![RouteHint(vec![RouteHintHop {
+                       src_node_id: nodes[2].clone(),
+                       short_channel_id: 5,
+                       fees: RoutingFees {
+                               base_msat: 100,
+                               proportional_millionths: 0,
+                       },
+                       cltv_expiry_delta: (5 << 8) | 1,
+                       htlc_minimum_msat: None,
+                       htlc_maximum_msat: None,
+               }, RouteHintHop {
+                       src_node_id: nodes[3].clone(),
+                       short_channel_id: 8,
+                       fees: zero_fees,
+                       cltv_expiry_delta: (8 << 8) | 1,
+                       htlc_minimum_msat: None,
+                       htlc_maximum_msat: None,
+               }]), RouteHint(vec![RouteHintHop {
+                       src_node_id: nodes[5].clone(),
+                       short_channel_id: 10,
+                       fees: zero_fees,
+                       cltv_expiry_delta: (10 << 8) | 1,
+                       htlc_minimum_msat: None,
+                       htlc_maximum_msat: None,
+               }])]
+       }
+
+       #[test]
+       fn multi_hint_last_hops_test() {
+               let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
+               let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
+               // Test through channels 2, 3, 5, 8.
+               // Test shows that multiple hop hints are considered.
+
+               // Disabling channels 6 & 7 by flags=2
+               update_channel(&net_graph_msg_handler, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
+                       chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+                       short_channel_id: 6,
+                       timestamp: 2,
+                       flags: 2, // to disable
+                       cltv_expiry_delta: 0,
+                       htlc_minimum_msat: 0,
+                       htlc_maximum_msat: OptionalField::Absent,
+                       fee_base_msat: 0,
+                       fee_proportional_millionths: 0,
+                       excess_data: Vec::new()
+               });
+               update_channel(&net_graph_msg_handler, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
+                       chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+                       short_channel_id: 7,
+                       timestamp: 2,
+                       flags: 2, // to disable
+                       cltv_expiry_delta: 0,
+                       htlc_minimum_msat: 0,
+                       htlc_maximum_msat: OptionalField::Absent,
+                       fee_base_msat: 0,
+                       fee_proportional_millionths: 0,
+                       excess_data: Vec::new()
+               });
+
+               let route = get_route(&our_id, &net_graph_msg_handler.network_graph.read().unwrap(), &nodes[6], None, None, &multi_hint_last_hops(&nodes).iter().collect::<Vec<_>>(), 100, 42, Arc::clone(&logger)).unwrap();
+               assert_eq!(route.paths[0].len(), 4);
+
+               assert_eq!(route.paths[0][0].pubkey, nodes[1]);
+               assert_eq!(route.paths[0][0].short_channel_id, 2);
+               assert_eq!(route.paths[0][0].fee_msat, 200);
+               assert_eq!(route.paths[0][0].cltv_expiry_delta, 1025);
+               assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags(2));
+               assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags(2));
+
+               assert_eq!(route.paths[0][1].pubkey, nodes[2]);
+               assert_eq!(route.paths[0][1].short_channel_id, 4);
+               assert_eq!(route.paths[0][1].fee_msat, 100);
+               assert_eq!(route.paths[0][1].cltv_expiry_delta, 1281);
+               assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags(3));
+               assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags(4));
+
+               assert_eq!(route.paths[0][2].pubkey, nodes[3]);
+               assert_eq!(route.paths[0][2].short_channel_id, 5);
+               assert_eq!(route.paths[0][2].fee_msat, 0);
+               assert_eq!(route.paths[0][2].cltv_expiry_delta, 2049);
+               assert_eq!(route.paths[0][2].node_features.le_flags(), &id_to_feature_flags(4));
+               assert_eq!(route.paths[0][2].channel_features.le_flags(), &Vec::<u8>::new());
+
+               assert_eq!(route.paths[0][3].pubkey, nodes[6]);
+               assert_eq!(route.paths[0][3].short_channel_id, 8);
+               assert_eq!(route.paths[0][3].fee_msat, 100);
+               assert_eq!(route.paths[0][3].cltv_expiry_delta, 42);
+               assert_eq!(route.paths[0][3].node_features.le_flags(), &Vec::<u8>::new()); // We dont pass flags in from invoices yet
+               assert_eq!(route.paths[0][3].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
+       }
+
+       fn last_hops_with_public_channel(nodes: &Vec<PublicKey>) -> Vec<RouteHint> {
+               let zero_fees = RoutingFees {
+                       base_msat: 0,
+                       proportional_millionths: 0,
+               };
+               vec![RouteHint(vec![RouteHintHop {
+                       src_node_id: nodes[4].clone(),
+                       short_channel_id: 11,
+                       fees: zero_fees,
+                       cltv_expiry_delta: (11 << 8) | 1,
+                       htlc_minimum_msat: None,
+                       htlc_maximum_msat: None,
+               }, RouteHintHop {
+                       src_node_id: nodes[3].clone(),
+                       short_channel_id: 8,
+                       fees: zero_fees,
+                       cltv_expiry_delta: (8 << 8) | 1,
+                       htlc_minimum_msat: None,
+                       htlc_maximum_msat: None,
+               }]), RouteHint(vec![RouteHintHop {
+                       src_node_id: nodes[4].clone(),
+                       short_channel_id: 9,
+                       fees: RoutingFees {
+                               base_msat: 1001,
+                               proportional_millionths: 0,
+                       },
+                       cltv_expiry_delta: (9 << 8) | 1,
+                       htlc_minimum_msat: None,
+                       htlc_maximum_msat: None,
+               }]), RouteHint(vec![RouteHintHop {
+                       src_node_id: nodes[5].clone(),
+                       short_channel_id: 10,
+                       fees: zero_fees,
+                       cltv_expiry_delta: (10 << 8) | 1,
+                       htlc_minimum_msat: None,
+                       htlc_maximum_msat: None,
+               }])]
+       }
+
+       #[test]
+       fn last_hops_with_public_channel_test() {
+               let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
+               let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
+               // This test shows that public routes can be present in the invoice
+               // which would be handled in the same manner.
+
+               let route = get_route(&our_id, &net_graph_msg_handler.network_graph.read().unwrap(), &nodes[6], None, None, &last_hops_with_public_channel(&nodes).iter().collect::<Vec<_>>(), 100, 42, Arc::clone(&logger)).unwrap();
+               assert_eq!(route.paths[0].len(), 5);
+
+               assert_eq!(route.paths[0][0].pubkey, nodes[1]);
+               assert_eq!(route.paths[0][0].short_channel_id, 2);
+               assert_eq!(route.paths[0][0].fee_msat, 100);
+               assert_eq!(route.paths[0][0].cltv_expiry_delta, (4 << 8) | 1);
+               assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags(2));
+               assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags(2));
+
+               assert_eq!(route.paths[0][1].pubkey, nodes[2]);
+               assert_eq!(route.paths[0][1].short_channel_id, 4);
+               assert_eq!(route.paths[0][1].fee_msat, 0);
+               assert_eq!(route.paths[0][1].cltv_expiry_delta, (6 << 8) | 1);
+               assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags(3));
+               assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags(4));
+
+               assert_eq!(route.paths[0][2].pubkey, nodes[4]);
+               assert_eq!(route.paths[0][2].short_channel_id, 6);
+               assert_eq!(route.paths[0][2].fee_msat, 0);
+               assert_eq!(route.paths[0][2].cltv_expiry_delta, (11 << 8) | 1);
+               assert_eq!(route.paths[0][2].node_features.le_flags(), &id_to_feature_flags(5));
+               assert_eq!(route.paths[0][2].channel_features.le_flags(), &id_to_feature_flags(6));
+
+               assert_eq!(route.paths[0][3].pubkey, nodes[3]);
+               assert_eq!(route.paths[0][3].short_channel_id, 11);
+               assert_eq!(route.paths[0][3].fee_msat, 0);
+               assert_eq!(route.paths[0][3].cltv_expiry_delta, (8 << 8) | 1);
+               // If we have a peer in the node map, we'll use their features here since we don't have
+               // a way of figuring out their features from the invoice:
+               assert_eq!(route.paths[0][3].node_features.le_flags(), &id_to_feature_flags(4));
+               assert_eq!(route.paths[0][3].channel_features.le_flags(), &Vec::<u8>::new());
+
+               assert_eq!(route.paths[0][4].pubkey, nodes[6]);
+               assert_eq!(route.paths[0][4].short_channel_id, 8);
+               assert_eq!(route.paths[0][4].fee_msat, 100);
+               assert_eq!(route.paths[0][4].cltv_expiry_delta, 42);
+               assert_eq!(route.paths[0][4].node_features.le_flags(), &Vec::<u8>::new()); // We dont pass flags in from invoices yet
+               assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
+       }
+
        #[test]
        fn our_chans_last_hop_connect_test() {
                let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
index baa9e7cd16d98b05be00abcf5d7ebc46a75cbbf8..b7dfe767057836cdd59dd61c5bae32d88c362c6a 100644 (file)
@@ -11,10 +11,10 @@ use ln::chan_utils::{HTLCOutputInCommitment, ChannelPublicKeys, HolderCommitment
 use ln::{chan_utils, msgs};
 use chain::keysinterface::{Sign, InMemorySigner, BaseSign};
 
-use io;
 use prelude::*;
 use core::cmp;
 use sync::{Mutex, Arc};
+#[cfg(test)] use sync::MutexGuard;
 
 use bitcoin::blockdata::transaction::{Transaction, SigHashType};
 use bitcoin::util::bip143;
@@ -22,9 +22,8 @@ use bitcoin::util::bip143;
 use bitcoin::secp256k1;
 use bitcoin::secp256k1::key::{SecretKey, PublicKey};
 use bitcoin::secp256k1::{Secp256k1, Signature};
-use util::ser::{Writeable, Writer, Readable};
+use util::ser::{Writeable, Writer};
 use io::Error;
-use ln::msgs::DecodeError;
 
 /// Initial value for revoked commitment downward counter
 pub const INITIAL_REVOKED_COMMITMENT_NUMBER: u64 = 1 << 48;
@@ -35,31 +34,34 @@ pub const INITIAL_REVOKED_COMMITMENT_NUMBER: u64 = 1 << 48;
 /// - When signing, the holder transaction has not been revoked
 /// - When revoking, the holder transaction has not been signed
 /// - The holder commitment number is monotonic and without gaps
+/// - The revoked holder commitment number is monotonic and without gaps
+/// - There is at least one unrevoked holder transaction at all times
 /// - The counterparty commitment number is monotonic and without gaps
 /// - The pre-derived keys and pre-built transaction in CommitmentTransaction were correctly built
 ///
 /// Eventually we will probably want to expose a variant of this which would essentially
 /// be what you'd want to run on a hardware wallet.
 ///
+/// Note that counterparty signatures on the holder transaction are not checked, but it should
+/// be in a complete implementation.
+///
 /// Note that before we do so we should ensure its serialization format has backwards- and
 /// forwards-compatibility prefix/suffixes!
 #[derive(Clone)]
 pub struct EnforcingSigner {
        pub inner: InMemorySigner,
-       /// The last counterparty commitment number we signed, backwards counting
-       pub last_commitment_number: Arc<Mutex<Option<u64>>>,
-       /// The last holder commitment number we revoked, backwards counting
-       pub revoked_commitment: Arc<Mutex<u64>>,
+       /// Channel state used for policy enforcement
+       pub state: Arc<Mutex<EnforcementState>>,
        pub disable_revocation_policy_check: bool,
 }
 
 impl EnforcingSigner {
        /// Construct an EnforcingSigner
        pub fn new(inner: InMemorySigner) -> Self {
+               let state = Arc::new(Mutex::new(EnforcementState::new()));
                Self {
                        inner,
-                       last_commitment_number: Arc::new(Mutex::new(None)),
-                       revoked_commitment: Arc::new(Mutex::new(INITIAL_REVOKED_COMMITMENT_NUMBER)),
+                       state,
                        disable_revocation_policy_check: false
                }
        }
@@ -67,16 +69,20 @@ impl EnforcingSigner {
        /// Construct an EnforcingSigner with externally managed storage
        ///
        /// Since there are multiple copies of this struct for each channel, some coordination is needed
-       /// so that all copies are aware of revocations.  A pointer to this state is provided here, usually
-       /// by an implementation of KeysInterface.
-       pub fn new_with_revoked(inner: InMemorySigner, revoked_commitment: Arc<Mutex<u64>>, disable_revocation_policy_check: bool) -> Self {
+       /// so that all copies are aware of enforcement state.  A pointer to this state is provided
+       /// here, usually by an implementation of KeysInterface.
+       pub fn new_with_revoked(inner: InMemorySigner, state: Arc<Mutex<EnforcementState>>, disable_revocation_policy_check: bool) -> Self {
                Self {
                        inner,
-                       last_commitment_number: Arc::new(Mutex::new(None)),
-                       revoked_commitment,
+                       state,
                        disable_revocation_policy_check
                }
        }
+
+       #[cfg(test)]
+       pub fn get_enforcement_state(&self) -> MutexGuard<EnforcementState> {
+               self.state.lock().unwrap()
+       }
 }
 
 impl BaseSign for EnforcingSigner {
@@ -86,13 +92,22 @@ impl BaseSign for EnforcingSigner {
 
        fn release_commitment_secret(&self, idx: u64) -> [u8; 32] {
                {
-                       let mut revoked = self.revoked_commitment.lock().unwrap();
-                       assert!(idx == *revoked || idx == *revoked - 1, "can only revoke the current or next unrevoked commitment - trying {}, revoked {}", idx, *revoked);
-                       *revoked = idx;
+                       let mut state = self.state.lock().unwrap();
+                       assert!(idx == state.last_holder_revoked_commitment || idx == state.last_holder_revoked_commitment - 1, "can only revoke the current or next unrevoked commitment - trying {}, last revoked {}", idx, state.last_holder_revoked_commitment);
+                       assert!(idx > state.last_holder_commitment, "cannot revoke the last holder commitment - attempted to revoke {} last commitment {}", idx, state.last_holder_commitment);
+                       state.last_holder_revoked_commitment = idx;
                }
                self.inner.release_commitment_secret(idx)
        }
 
+       fn validate_holder_commitment(&self, holder_tx: &HolderCommitmentTransaction) -> Result<(), ()> {
+               let mut state = self.state.lock().unwrap();
+               let idx = holder_tx.commitment_number();
+               assert!(idx == state.last_holder_commitment || idx == state.last_holder_commitment - 1, "expecting to validate the current or next holder commitment - trying {}, current {}", idx, state.last_holder_commitment);
+               state.last_holder_commitment = idx;
+               Ok(())
+       }
+
        fn pubkeys(&self) -> &ChannelPublicKeys { self.inner.pubkeys() }
        fn channel_keys_id(&self) -> [u8; 32] { self.inner.channel_keys_id() }
 
@@ -100,29 +115,39 @@ impl BaseSign for EnforcingSigner {
                self.verify_counterparty_commitment_tx(commitment_tx, secp_ctx);
 
                {
-                       let mut last_commitment_number_guard = self.last_commitment_number.lock().unwrap();
+                       let mut state = self.state.lock().unwrap();
                        let actual_commitment_number = commitment_tx.commitment_number();
-                       let last_commitment_number = last_commitment_number_guard.unwrap_or(actual_commitment_number);
+                       let last_commitment_number = state.last_counterparty_commitment;
                        // These commitment numbers are backwards counting.  We expect either the same as the previously encountered,
                        // or the next one.
                        assert!(last_commitment_number == actual_commitment_number || last_commitment_number - 1 == actual_commitment_number, "{} doesn't come after {}", actual_commitment_number, last_commitment_number);
-                       *last_commitment_number_guard = Some(cmp::min(last_commitment_number, actual_commitment_number))
+                       // Ensure that the counterparty doesn't get more than two broadcastable commitments -
+                       // the last and the one we are trying to sign
+                       assert!(actual_commitment_number >= state.last_counterparty_revoked_commitment - 2, "cannot sign a commitment if second to last wasn't revoked - signing {} revoked {}", actual_commitment_number, state.last_counterparty_revoked_commitment);
+                       state.last_counterparty_commitment = cmp::min(last_commitment_number, actual_commitment_number)
                }
 
                Ok(self.inner.sign_counterparty_commitment(commitment_tx, secp_ctx).unwrap())
        }
 
+       fn validate_counterparty_revocation(&self, idx: u64, _secret: &SecretKey) -> Result<(), ()> {
+               let mut state = self.state.lock().unwrap();
+               assert!(idx == state.last_counterparty_revoked_commitment || idx == state.last_counterparty_revoked_commitment - 1, "expecting to validate the current or next counterparty revocation - trying {}, current {}", idx, state.last_counterparty_revoked_commitment);
+               state.last_counterparty_revoked_commitment = idx;
+               Ok(())
+       }
+
        fn sign_holder_commitment_and_htlcs(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<(Signature, Vec<Signature>), ()> {
                let trusted_tx = self.verify_holder_commitment_tx(commitment_tx, secp_ctx);
                let commitment_txid = trusted_tx.txid();
                let holder_csv = self.inner.counterparty_selected_contest_delay();
 
-               let revoked = self.revoked_commitment.lock().unwrap();
+               let state = self.state.lock().unwrap();
                let commitment_number = trusted_tx.commitment_number();
-               if *revoked - 1 != commitment_number && *revoked - 2 != commitment_number {
+               if state.last_holder_revoked_commitment - 1 != commitment_number && state.last_holder_revoked_commitment - 2 != commitment_number {
                        if !self.disable_revocation_policy_check {
                                panic!("can only sign the next two unrevoked commitment numbers, revoked={} vs requested={} for {}",
-                                      *revoked, commitment_number, self.inner.commitment_seed[0])
+                                      state.last_holder_revoked_commitment, commitment_number, self.inner.commitment_seed[0])
                        }
                }
 
@@ -174,26 +199,15 @@ impl Sign for EnforcingSigner {}
 
 impl Writeable for EnforcingSigner {
        fn write<W: Writer>(&self, writer: &mut W) -> Result<(), Error> {
+               // EnforcingSigner has two fields - `inner` ([`InMemorySigner`]) and `state`
+               // ([`EnforcementState`]). `inner` is serialized here and deserialized by
+               // [`KeysInterface::read_chan_signer`]. `state` is managed by [`KeysInterface`]
+               // and will be serialized as needed by the implementation of that trait.
                self.inner.write(writer)?;
-               let last = *self.last_commitment_number.lock().unwrap();
-               last.write(writer)?;
                Ok(())
        }
 }
 
-impl Readable for EnforcingSigner {
-       fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
-               let inner = Readable::read(reader)?;
-               let last_commitment_number = Readable::read(reader)?;
-               Ok(EnforcingSigner {
-                       inner,
-                       last_commitment_number: Arc::new(Mutex::new(last_commitment_number)),
-                       revoked_commitment: Arc::new(Mutex::new(INITIAL_REVOKED_COMMITMENT_NUMBER)),
-                       disable_revocation_policy_check: false,
-               })
-       }
-}
-
 impl EnforcingSigner {
        fn verify_counterparty_commitment_tx<'a, T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &'a CommitmentTransaction, secp_ctx: &Secp256k1<T>) -> TrustedCommitmentTransaction<'a> {
                commitment_tx.verify(&self.inner.get_channel_parameters().as_counterparty_broadcastable(),
@@ -207,3 +221,31 @@ impl EnforcingSigner {
                        .expect("derived different per-tx keys or built transaction")
        }
 }
+
+/// The state used by [`EnforcingSigner`] in order to enforce policy checks
+///
+/// This structure is maintained by KeysInterface since we may have multiple copies of
+/// the signer and they must coordinate their state.
+#[derive(Clone)]
+pub struct EnforcementState {
+       /// The last counterparty commitment number we signed, backwards counting
+       pub last_counterparty_commitment: u64,
+       /// The last counterparty commitment they revoked, backwards counting
+       pub last_counterparty_revoked_commitment: u64,
+       /// The last holder commitment number we revoked, backwards counting
+       pub last_holder_revoked_commitment: u64,
+       /// The last validated holder commitment number, backwards counting
+       pub last_holder_commitment: u64,
+}
+
+impl EnforcementState {
+       /// Enforcement state for a new channel
+       pub fn new() -> Self {
+               EnforcementState {
+                       last_counterparty_commitment: INITIAL_REVOKED_COMMITMENT_NUMBER,
+                       last_counterparty_revoked_commitment: INITIAL_REVOKED_COMMITMENT_NUMBER,
+                       last_holder_revoked_commitment: INITIAL_REVOKED_COMMITMENT_NUMBER,
+                       last_holder_commitment: INITIAL_REVOKED_COMMITMENT_NUMBER,
+               }
+       }
+}
index 15157e50e556554a38988d67e2a38043d23a938f..64b88acb0081487495f997ac2706f9783dd62d9c 100644 (file)
@@ -20,7 +20,7 @@ use ln::features::{ChannelFeatures, InitFeatures};
 use ln::msgs;
 use ln::msgs::OptionalField;
 use ln::script::ShutdownScript;
-use util::enforcing_trait_impls::{EnforcingSigner, INITIAL_REVOKED_COMMITMENT_NUMBER};
+use util::enforcing_trait_impls::{EnforcingSigner, EnforcementState};
 use util::events;
 use util::logger::{Logger, Level, Record};
 use util::ser::{Readable, ReadableArgs, Writer, Writeable};
@@ -76,8 +76,15 @@ impl keysinterface::KeysInterface for OnlyReadsKeysInterface {
        fn get_channel_signer(&self, _inbound: bool, _channel_value_satoshis: u64) -> EnforcingSigner { unreachable!(); }
        fn get_secure_random_bytes(&self) -> [u8; 32] { [0; 32] }
 
-       fn read_chan_signer(&self, reader: &[u8]) -> Result<Self::Signer, msgs::DecodeError> {
-               EnforcingSigner::read(&mut io::Cursor::new(reader))
+       fn read_chan_signer(&self, mut reader: &[u8]) -> Result<Self::Signer, msgs::DecodeError> {
+               let inner: InMemorySigner = Readable::read(&mut reader)?;
+               let state = Arc::new(Mutex::new(EnforcementState::new()));
+
+               Ok(EnforcingSigner::new_with_revoked(
+                       inner,
+                       state,
+                       false
+               ))
        }
        fn sign_invoice(&self, _invoice_preimage: Vec<u8>) -> Result<RecoverableSignature, ()> { unreachable!(); }
 }
@@ -452,7 +459,7 @@ pub struct TestKeysInterface {
        pub override_session_priv: Mutex<Option<[u8; 32]>>,
        pub override_channel_id_priv: Mutex<Option<[u8; 32]>>,
        pub disable_revocation_policy_check: bool,
-       revoked_commitments: Mutex<HashMap<[u8;32], Arc<Mutex<u64>>>>,
+       enforcement_states: Mutex<HashMap<[u8;32], Arc<Mutex<EnforcementState>>>>,
        expectations: Mutex<Option<VecDeque<OnGetShutdownScriptpubkey>>>,
 }
 
@@ -474,8 +481,8 @@ impl keysinterface::KeysInterface for TestKeysInterface {
 
        fn get_channel_signer(&self, inbound: bool, channel_value_satoshis: u64) -> EnforcingSigner {
                let keys = self.backing.get_channel_signer(inbound, channel_value_satoshis);
-               let revoked_commitment = self.make_revoked_commitment_cell(keys.commitment_seed);
-               EnforcingSigner::new_with_revoked(keys, revoked_commitment, self.disable_revocation_policy_check)
+               let state = self.make_enforcement_state_cell(keys.commitment_seed);
+               EnforcingSigner::new_with_revoked(keys, state, self.disable_revocation_policy_check)
        }
 
        fn get_secure_random_bytes(&self) -> [u8; 32] {
@@ -497,16 +504,13 @@ impl keysinterface::KeysInterface for TestKeysInterface {
                let mut reader = io::Cursor::new(buffer);
 
                let inner: InMemorySigner = Readable::read(&mut reader)?;
-               let revoked_commitment = self.make_revoked_commitment_cell(inner.commitment_seed);
-
-               let last_commitment_number = Readable::read(&mut reader)?;
+               let state = self.make_enforcement_state_cell(inner.commitment_seed);
 
-               Ok(EnforcingSigner {
+               Ok(EnforcingSigner::new_with_revoked(
                        inner,
-                       last_commitment_number: Arc::new(Mutex::new(last_commitment_number)),
-                       revoked_commitment,
-                       disable_revocation_policy_check: self.disable_revocation_policy_check,
-               })
+                       state,
+                       self.disable_revocation_policy_check
+               ))
        }
 
        fn sign_invoice(&self, invoice_preimage: Vec<u8>) -> Result<RecoverableSignature, ()> {
@@ -522,7 +526,7 @@ impl TestKeysInterface {
                        override_session_priv: Mutex::new(None),
                        override_channel_id_priv: Mutex::new(None),
                        disable_revocation_policy_check: false,
-                       revoked_commitments: Mutex::new(HashMap::new()),
+                       enforcement_states: Mutex::new(HashMap::new()),
                        expectations: Mutex::new(None),
                }
        }
@@ -538,16 +542,17 @@ impl TestKeysInterface {
 
        pub fn derive_channel_keys(&self, channel_value_satoshis: u64, id: &[u8; 32]) -> EnforcingSigner {
                let keys = self.backing.derive_channel_keys(channel_value_satoshis, id);
-               let revoked_commitment = self.make_revoked_commitment_cell(keys.commitment_seed);
-               EnforcingSigner::new_with_revoked(keys, revoked_commitment, self.disable_revocation_policy_check)
+               let state = self.make_enforcement_state_cell(keys.commitment_seed);
+               EnforcingSigner::new_with_revoked(keys, state, self.disable_revocation_policy_check)
        }
 
-       fn make_revoked_commitment_cell(&self, commitment_seed: [u8; 32]) -> Arc<Mutex<u64>> {
-               let mut revoked_commitments = self.revoked_commitments.lock().unwrap();
-               if !revoked_commitments.contains_key(&commitment_seed) {
-                       revoked_commitments.insert(commitment_seed, Arc::new(Mutex::new(INITIAL_REVOKED_COMMITMENT_NUMBER)));
+       fn make_enforcement_state_cell(&self, commitment_seed: [u8; 32]) -> Arc<Mutex<EnforcementState>> {
+               let mut states = self.enforcement_states.lock().unwrap();
+               if !states.contains_key(&commitment_seed) {
+                       let state = EnforcementState::new();
+                       states.insert(commitment_seed, Arc::new(Mutex::new(state)));
                }
-               let cell = revoked_commitments.get(&commitment_seed).unwrap();
+               let cell = states.get(&commitment_seed).unwrap();
                Arc::clone(cell)
        }
 }