@@ -1509,3 +1509,220 @@ mod tests {
15091509 }
15101510 }
15111511}
1512+
1513+ #[ cfg( test) ]
1514+ mod bolt12_tests {
1515+ use super :: { Bolt12ParseError , Bolt12SemanticError , Offer } ;
1516+ use crate :: ln:: msgs:: DecodeError ;
1517+
1518+ #[ test]
1519+ fn parses_bech32_encoded_offers ( ) {
1520+ let offers = [
1521+ // Minimal bolt12 offer
1522+ "lno1pgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg" ,
1523+
1524+ // for testnet
1525+ "lno1qgsyxjtl6luzd9t3pr62xr7eemp6awnejusgf6gw45q75vcfqqqqqqq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj" ,
1526+
1527+ // for bitcoin (redundant)
1528+ "lno1qgsxlc5vp2m0rvmjcxn2y34wv0m5lyc7sdj7zksgn35dvxgqqqqqqqq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj" ,
1529+
1530+ // for bitcoin or liquidv1
1531+ "lno1qfqpge38tqmzyrdjj3x2qkdr5y80dlfw56ztq6yd9sme995g3gsxqqm0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq9qc4r9wd6zqan9vd6x7unnzcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese" ,
1532+
1533+ // with metadata
1534+ "lno1qsgqqqqqqqqqqqqqqqqqqqqqqqqqqzsv23jhxapqwejkxar0wfe3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs" ,
1535+
1536+ // with amount
1537+ "lno1pqpzwyq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj" ,
1538+
1539+ // with currency
1540+ "lno1qcp4256ypqpzwyq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj" ,
1541+
1542+ // with expiry
1543+ "lno1pgx9getnwss8vetrw3hhyucwq3ay997czcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese" ,
1544+
1545+ // with issuer
1546+ "lno1pgx9getnwss8vetrw3hhyucjy358garswvaz7tmzdak8gvfj9ehhyeeqgf85c4p3xgsxjmnyw4ehgunfv4e3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs" ,
1547+
1548+ // with quantity
1549+ "lno1pgx9getnwss8vetrw3hhyuc5qyz3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs" ,
1550+
1551+ // with unlimited (or unknown) quantity
1552+ "lno1pgx9getnwss8vetrw3hhyuc5qqtzzqhwcuj966ma9n9nqwqtl032xeyv6755yeflt235pmww58egx6rxry" ,
1553+
1554+ // with single quantity (weird but valid)
1555+ "lno1pgx9getnwss8vetrw3hhyuc5qyq3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs" ,
1556+
1557+ // with feature
1558+ "lno1pgx9getnwss8vetrw3hhyucvp5yqqqqqqqqqqqqqqqqqqqqkyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg" ,
1559+
1560+ // with blinded path via Bob (0x424242...), blinding 020202...
1561+ "lno1pgx9getnwss8vetrw3hhyucs5ypjgef743p5fzqq9nqxh0ah7y87rzv3ud0eleps9kl2d5348hq2k8qzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgqpqqqqqqqqqqqqqqqqqqqqqqqqqqqzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqqzq3zyg3zyg3zyg3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs" ,
1562+
1563+ // ... and with second blinded path via Carol (0x434343...), blinding 020202...
1564+ "lno1pgx9getnwss8vetrw3hhyucsl5q5yqeyv5l2cs6y3qqzesrth7mlzrlp3xg7xhulusczm04x6g6nms9trspqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqqsqqqqqqqqqqqqqqqqqqqqqqqqqqpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqpqg3zyg3zyg3zygz0uc7h32x9s0aecdhxlk075kn046aafpuuyw8f5j652t3vha2yqrsyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqzqqqqqqqqqqqqqqqqqqqqqqqqqqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqqyzyg3zyg3zyg3zzcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese" ,
1565+
1566+ // unknown odd field
1567+ "lno1pgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxfppf5x2mrvdamk7unvvs" ,
1568+ ] ;
1569+ for encoded_offer in & offers {
1570+ if let Err ( e) = encoded_offer. parse :: < Offer > ( ) {
1571+ panic ! ( "Invalid offer ({:?}): {}" , e, encoded_offer) ;
1572+ }
1573+ }
1574+ }
1575+
1576+ #[ test]
1577+ fn fails_parsing_bech32_encoded_offers ( ) {
1578+ // Malformed: fields out of order
1579+ assert_eq ! (
1580+ "lno1zcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszpgz5znzfgdzs" . parse:: <Offer >( ) ,
1581+ Err ( Bolt12ParseError :: Decode ( DecodeError :: InvalidValue ) ) ,
1582+ ) ;
1583+
1584+ // Malformed: unknown even TLV type 78
1585+ assert_eq ! (
1586+ "lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpysgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq" . parse:: <Offer >( ) ,
1587+ Err ( Bolt12ParseError :: Decode ( DecodeError :: UnknownRequiredFeature ) ) ,
1588+ ) ;
1589+
1590+ // Malformed: empty
1591+ assert_eq ! (
1592+ "lno1" . parse:: <Offer >( ) ,
1593+ Err ( Bolt12ParseError :: InvalidSemantics ( Bolt12SemanticError :: MissingDescription ) ) ,
1594+ ) ;
1595+
1596+ // Malformed: truncated at type
1597+ assert_eq ! (
1598+ "lno1pg" . parse:: <Offer >( ) ,
1599+ Err ( Bolt12ParseError :: Decode ( DecodeError :: ShortRead ) ) ,
1600+ ) ;
1601+
1602+ // Malformed: truncated in length
1603+ assert_eq ! (
1604+ "lno1pt7s" . parse:: <Offer >( ) ,
1605+ Err ( Bolt12ParseError :: Decode ( DecodeError :: ShortRead ) ) ,
1606+ ) ;
1607+
1608+ // Malformed: truncated after length
1609+ assert_eq ! (
1610+ "lno1pgpq" . parse:: <Offer >( ) ,
1611+ Err ( Bolt12ParseError :: Decode ( DecodeError :: ShortRead ) ) ,
1612+ ) ;
1613+
1614+ // Malformed: truncated in description
1615+ assert_eq ! (
1616+ "lno1pgpyz" . parse:: <Offer >( ) ,
1617+ Err ( Bolt12ParseError :: Decode ( DecodeError :: ShortRead ) ) ,
1618+ ) ;
1619+
1620+ // Malformed: invalid offer_chains length
1621+ assert_eq ! (
1622+ "lno1qgqszzs9g9xyjs69zcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz" . parse:: <Offer >( ) ,
1623+ Err ( Bolt12ParseError :: Decode ( DecodeError :: ShortRead ) ) ,
1624+ ) ;
1625+
1626+ // Malformed: truncated currency UTF-8
1627+ assert_eq ! (
1628+ "lno1qcqcqzs9g9xyjs69zcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz" . parse:: <Offer >( ) ,
1629+ Err ( Bolt12ParseError :: Decode ( DecodeError :: ShortRead ) ) ,
1630+ ) ;
1631+
1632+ // Malformed: invalid currency UTF-8
1633+ assert_eq ! (
1634+ "lno1qcpgqsg2q4q5cj2rg5tzzqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqg" . parse:: <Offer >( ) ,
1635+ Err ( Bolt12ParseError :: Decode ( DecodeError :: ShortRead ) ) ,
1636+ ) ;
1637+
1638+ // Malformed: truncated description UTF-8
1639+ assert_eq ! (
1640+ "lno1pgqcq93pqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqy" . parse:: <Offer >( ) ,
1641+ Err ( Bolt12ParseError :: Decode ( DecodeError :: InvalidValue ) ) ,
1642+ ) ;
1643+
1644+ // Malformed: invalid description UTF-8
1645+ assert_eq ! (
1646+ "lno1pgpgqsgkyypqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs" . parse:: <Offer >( ) ,
1647+ Err ( Bolt12ParseError :: Decode ( DecodeError :: InvalidValue ) ) ,
1648+ ) ;
1649+
1650+ // Malformed: truncated offer_paths
1651+ assert_eq ! (
1652+ "lno1pgz5znzfgdz3qqgpzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz" . parse:: <Offer >( ) ,
1653+ Err ( Bolt12ParseError :: Decode ( DecodeError :: ShortRead ) ) ,
1654+ ) ;
1655+
1656+ // Malformed: zero num_hops in blinded_path
1657+ assert_eq ! (
1658+ "lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz" . parse:: <Offer >( ) ,
1659+ Err ( Bolt12ParseError :: Decode ( DecodeError :: ShortRead ) ) ,
1660+ ) ;
1661+
1662+ // Malformed: truncated onionmsg_hop in blinded_path
1663+ assert_eq ! (
1664+ "lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqspqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqgkyypqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs" . parse:: <Offer >( ) ,
1665+ Err ( Bolt12ParseError :: Decode ( DecodeError :: ShortRead ) ) ,
1666+ ) ;
1667+
1668+ // Malformed: bad first_node_id in blinded_path
1669+ assert_eq ! (
1670+ "lno1pgz5znzfgdz3qqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqspqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqgqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz" . parse:: <Offer >( ) ,
1671+ Err ( Bolt12ParseError :: Decode ( DecodeError :: ShortRead ) ) ,
1672+ ) ;
1673+
1674+ // Malformed: bad blinding in blinded_path
1675+ assert_eq ! (
1676+ "lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcpqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqgqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz" . parse:: <Offer >( ) ,
1677+ Err ( Bolt12ParseError :: Decode ( DecodeError :: ShortRead ) ) ,
1678+ ) ;
1679+
1680+ // Malformed: bad blinded_node_id in onionmsg_hop
1681+ assert_eq ! (
1682+ "lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqspqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqgqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz" . parse:: <Offer >( ) ,
1683+ Err ( Bolt12ParseError :: Decode ( DecodeError :: ShortRead ) ) ,
1684+ ) ;
1685+
1686+ // Malformed: truncated issuer UTF-8
1687+ assert_eq ! (
1688+ "lno1pgz5znzfgdz3yqvqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz" . parse:: <Offer >( ) ,
1689+ Err ( Bolt12ParseError :: Decode ( DecodeError :: InvalidValue ) ) ,
1690+ ) ;
1691+
1692+ // Malformed: invalid issuer UTF-8
1693+ assert_eq ! (
1694+ "lno1pgz5znzfgdz3yq5qgytzzqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqg" . parse:: <Offer >( ) ,
1695+ Err ( Bolt12ParseError :: Decode ( DecodeError :: InvalidValue ) ) ,
1696+ ) ;
1697+
1698+ // Malformed: invalid offer_node_id
1699+ assert_eq ! (
1700+ "lno1pgz5znzfgdz3vggzqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvps" . parse:: <Offer >( ) ,
1701+ Err ( Bolt12ParseError :: Decode ( DecodeError :: InvalidValue ) ) ,
1702+ ) ;
1703+
1704+ // Contains type >= 80
1705+ assert_eq ! (
1706+ "lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgp9qgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq" . parse:: <Offer >( ) ,
1707+ Err ( Bolt12ParseError :: Decode ( DecodeError :: InvalidValue ) ) ,
1708+ ) ;
1709+
1710+ // TODO: Resolved in spec https://github.com/lightning/bolts/pull/798/files#r1334851959
1711+ // Contains unknown feature 22
1712+ assert ! (
1713+ "lno1pgx9getnwss8vetrw3hhyucvqdqqqqqkyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg" . parse:: <Offer >( ) . is_ok( )
1714+ ) ;
1715+
1716+ // Missing offer_description
1717+ assert_eq ! (
1718+ "lno1zcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese" . parse:: <Offer >( ) ,
1719+ Err ( Bolt12ParseError :: InvalidSemantics ( Bolt12SemanticError :: MissingDescription ) ) ,
1720+ ) ;
1721+
1722+ // Missing offer_node_id"
1723+ assert_eq ! (
1724+ "lno1pgx9getnwss8vetrw3hhyuc" . parse:: <Offer >( ) ,
1725+ Err ( Bolt12ParseError :: InvalidSemantics ( Bolt12SemanticError :: MissingSigningPubkey ) ) ,
1726+ ) ;
1727+ }
1728+ }
0 commit comments