@@ -1509,3 +1509,220 @@ mod tests {
1509
1509
}
1510
1510
}
1511
1511
}
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