BOLT 12 test vectors for offer parsing
authorJeffrey Czyz <jkczyz@gmail.com>
Tue, 26 Sep 2023 20:42:28 +0000 (15:42 -0500)
committerJeffrey Czyz <jkczyz@gmail.com>
Tue, 24 Oct 2023 20:45:35 +0000 (15:45 -0500)
One discrepancy from the spec still needs to be resolved:

https://github.com/lightning/bolts/pull/798/files#r1334851959

lightning/src/offers/offer.rs

index 64e413679b672c3c34c8d9c4d33e85e965a12a6d..9772ee41758ac6fa9b66dbe10958b0b54ad9fc80 100644 (file)
@@ -1509,3 +1509,220 @@ mod tests {
                }
        }
 }
+
+#[cfg(test)]
+mod bolt12_tests {
+       use super::{Bolt12ParseError, Bolt12SemanticError, Offer};
+       use crate::ln::msgs::DecodeError;
+
+       #[test]
+       fn parses_bech32_encoded_offers() {
+               let offers = [
+                       // Minimal bolt12 offer
+                       "lno1pgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
+
+                       // for testnet
+                       "lno1qgsyxjtl6luzd9t3pr62xr7eemp6awnejusgf6gw45q75vcfqqqqqqq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj",
+
+                       // for bitcoin (redundant)
+                       "lno1qgsxlc5vp2m0rvmjcxn2y34wv0m5lyc7sdj7zksgn35dvxgqqqqqqqq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj",
+
+                       // for bitcoin or liquidv1
+                       "lno1qfqpge38tqmzyrdjj3x2qkdr5y80dlfw56ztq6yd9sme995g3gsxqqm0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq9qc4r9wd6zqan9vd6x7unnzcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese",
+
+                       // with metadata
+                       "lno1qsgqqqqqqqqqqqqqqqqqqqqqqqqqqzsv23jhxapqwejkxar0wfe3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs",
+
+                       // with amount
+                       "lno1pqpzwyq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj",
+
+                       // with currency
+                       "lno1qcp4256ypqpzwyq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj",
+
+                       // with expiry
+                       "lno1pgx9getnwss8vetrw3hhyucwq3ay997czcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese",
+
+                       // with issuer
+                       "lno1pgx9getnwss8vetrw3hhyucjy358garswvaz7tmzdak8gvfj9ehhyeeqgf85c4p3xgsxjmnyw4ehgunfv4e3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs",
+
+                       // with quantity
+                       "lno1pgx9getnwss8vetrw3hhyuc5qyz3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs",
+
+                       // with unlimited (or unknown) quantity
+                       "lno1pgx9getnwss8vetrw3hhyuc5qqtzzqhwcuj966ma9n9nqwqtl032xeyv6755yeflt235pmww58egx6rxry",
+
+                       // with single quantity (weird but valid)
+                       "lno1pgx9getnwss8vetrw3hhyuc5qyq3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs",
+
+                       // with feature
+                       "lno1pgx9getnwss8vetrw3hhyucvp5yqqqqqqqqqqqqqqqqqqqqkyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
+
+                       // with blinded path via Bob (0x424242...), blinding 020202...
+                       "lno1pgx9getnwss8vetrw3hhyucs5ypjgef743p5fzqq9nqxh0ah7y87rzv3ud0eleps9kl2d5348hq2k8qzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgqpqqqqqqqqqqqqqqqqqqqqqqqqqqqzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqqzq3zyg3zyg3zyg3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs",
+
+                       // ... and with second blinded path via Carol (0x434343...), blinding 020202...
+                       "lno1pgx9getnwss8vetrw3hhyucsl5q5yqeyv5l2cs6y3qqzesrth7mlzrlp3xg7xhulusczm04x6g6nms9trspqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqqsqqqqqqqqqqqqqqqqqqqqqqqqqqpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqpqg3zyg3zyg3zygz0uc7h32x9s0aecdhxlk075kn046aafpuuyw8f5j652t3vha2yqrsyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqzqqqqqqqqqqqqqqqqqqqqqqqqqqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqqyzyg3zyg3zyg3zzcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese",
+
+                       // unknown odd field
+                       "lno1pgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxfppf5x2mrvdamk7unvvs",
+               ];
+               for encoded_offer in &offers {
+                       if let Err(e) = encoded_offer.parse::<Offer>() {
+                               panic!("Invalid offer ({:?}): {}", e, encoded_offer);
+                       }
+               }
+       }
+
+       #[test]
+       fn fails_parsing_bech32_encoded_offers() {
+               // Malformed: fields out of order
+               assert_eq!(
+                       "lno1zcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszpgz5znzfgdzs".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
+               );
+
+               // Malformed: unknown even TLV type 78
+               assert_eq!(
+                       "lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpysgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
+               );
+
+               // Malformed: empty
+               assert_eq!(
+                       "lno1".parse::<Offer>(),
+                       Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingDescription)),
+               );
+
+               // Malformed: truncated at type
+               assert_eq!(
+                       "lno1pg".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+               );
+
+               // Malformed: truncated in length
+               assert_eq!(
+                       "lno1pt7s".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+               );
+
+               // Malformed: truncated after length
+               assert_eq!(
+                       "lno1pgpq".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+               );
+
+               // Malformed: truncated in description
+               assert_eq!(
+                       "lno1pgpyz".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+               );
+
+               // Malformed: invalid offer_chains length
+               assert_eq!(
+                       "lno1qgqszzs9g9xyjs69zcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+               );
+
+               // Malformed: truncated currency UTF-8
+               assert_eq!(
+                       "lno1qcqcqzs9g9xyjs69zcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+               );
+
+               // Malformed: invalid currency UTF-8
+               assert_eq!(
+                       "lno1qcpgqsg2q4q5cj2rg5tzzqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqg".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+               );
+
+               // Malformed: truncated description UTF-8
+               assert_eq!(
+                       "lno1pgqcq93pqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqy".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
+               );
+
+               // Malformed: invalid description UTF-8
+               assert_eq!(
+                       "lno1pgpgqsgkyypqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
+               );
+
+               // Malformed: truncated offer_paths
+               assert_eq!(
+                       "lno1pgz5znzfgdz3qqgpzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+               );
+
+               // Malformed: zero num_hops in blinded_path
+               assert_eq!(
+                       "lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+               );
+
+               // Malformed: truncated onionmsg_hop in blinded_path
+               assert_eq!(
+                       "lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqspqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqgkyypqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+               );
+
+               // Malformed: bad first_node_id in blinded_path
+               assert_eq!(
+                       "lno1pgz5znzfgdz3qqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqspqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqgqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+               );
+
+               // Malformed: bad blinding in blinded_path
+               assert_eq!(
+                       "lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcpqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqgqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+               );
+
+               // Malformed: bad blinded_node_id in onionmsg_hop
+               assert_eq!(
+                       "lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqspqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqgqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
+               );
+
+               // Malformed: truncated issuer UTF-8
+               assert_eq!(
+                       "lno1pgz5znzfgdz3yqvqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
+               );
+
+               // Malformed: invalid issuer UTF-8
+               assert_eq!(
+                       "lno1pgz5znzfgdz3yq5qgytzzqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqg".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
+               );
+
+               // Malformed: invalid offer_node_id
+               assert_eq!(
+                       "lno1pgz5znzfgdz3vggzqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvps".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
+               );
+
+               // Contains type >= 80
+               assert_eq!(
+                       "lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgp9qgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq".parse::<Offer>(),
+                       Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
+               );
+
+               // TODO: Resolved in spec https://github.com/lightning/bolts/pull/798/files#r1334851959
+               // Contains unknown feature 22
+               assert!(
+                       "lno1pgx9getnwss8vetrw3hhyucvqdqqqqqkyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg".parse::<Offer>().is_ok()
+               );
+
+               // Missing offer_description
+               assert_eq!(
+                       "lno1zcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese".parse::<Offer>(),
+                       Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingDescription)),
+               );
+
+               // Missing offer_node_id"
+               assert_eq!(
+                       "lno1pgx9getnwss8vetrw3hhyuc".parse::<Offer>(),
+                       Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSigningPubkey)),
+               );
+       }
+}