Skip to content

Commit 415cbf0

Browse files
Merge pull request #2682 from jkczyz/2023-09-bolt12-test-vectors
BOLT 12 Offer test vectors
2 parents d0f0d9c + e469492 commit 415cbf0

File tree

2 files changed

+285
-56
lines changed

2 files changed

+285
-56
lines changed

lightning/src/offers/offer.rs

+195-56
Original file line numberDiff line numberDiff line change
@@ -1519,28 +1519,60 @@ mod tests {
15191519
}
15201520

15211521
#[cfg(test)]
1522-
mod bech32_tests {
1523-
use super::{Bolt12ParseError, Offer};
1524-
use bitcoin::bech32;
1522+
mod bolt12_tests {
1523+
use super::{Bolt12ParseError, Bolt12SemanticError, Offer};
15251524
use crate::ln::msgs::DecodeError;
15261525

1527-
#[test]
1528-
fn encodes_offer_as_bech32_without_checksum() {
1529-
let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
1530-
let offer = dbg!(encoded_offer.parse::<Offer>().unwrap());
1531-
let reencoded_offer = offer.to_string();
1532-
dbg!(reencoded_offer.parse::<Offer>().unwrap());
1533-
assert_eq!(reencoded_offer, encoded_offer);
1534-
}
1535-
15361526
#[test]
15371527
fn parses_bech32_encoded_offers() {
15381528
let offers = [
1539-
// BOLT 12 test vectors
1540-
"lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
1541-
"l+no1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
1542-
"lno1pqps7sjqpgt+yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+5k7msjzfpy7nz5yqcn+ygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+5xvxg",
1543-
"lno1pqps7sjqpgt+ yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+ 5k7msjzfpy7nz5yqcn+\nygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+\r\n 5xvxg",
1529+
// Minimal bolt12 offer
1530+
"lno1pgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
1531+
1532+
// for testnet
1533+
"lno1qgsyxjtl6luzd9t3pr62xr7eemp6awnejusgf6gw45q75vcfqqqqqqq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj",
1534+
1535+
// for bitcoin (redundant)
1536+
"lno1qgsxlc5vp2m0rvmjcxn2y34wv0m5lyc7sdj7zksgn35dvxgqqqqqqqq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj",
1537+
1538+
// for bitcoin or liquidv1
1539+
"lno1qfqpge38tqmzyrdjj3x2qkdr5y80dlfw56ztq6yd9sme995g3gsxqqm0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq9qc4r9wd6zqan9vd6x7unnzcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese",
1540+
1541+
// with metadata
1542+
"lno1qsgqqqqqqqqqqqqqqqqqqqqqqqqqqzsv23jhxapqwejkxar0wfe3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs",
1543+
1544+
// with amount
1545+
"lno1pqpzwyq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj",
1546+
1547+
// with currency
1548+
"lno1qcp4256ypqpzwyq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj",
1549+
1550+
// with expiry
1551+
"lno1pgx9getnwss8vetrw3hhyucwq3ay997czcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese",
1552+
1553+
// with issuer
1554+
"lno1pgx9getnwss8vetrw3hhyucjy358garswvaz7tmzdak8gvfj9ehhyeeqgf85c4p3xgsxjmnyw4ehgunfv4e3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs",
1555+
1556+
// with quantity
1557+
"lno1pgx9getnwss8vetrw3hhyuc5qyz3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs",
1558+
1559+
// with unlimited (or unknown) quantity
1560+
"lno1pgx9getnwss8vetrw3hhyuc5qqtzzqhwcuj966ma9n9nqwqtl032xeyv6755yeflt235pmww58egx6rxry",
1561+
1562+
// with single quantity (weird but valid)
1563+
"lno1pgx9getnwss8vetrw3hhyuc5qyq3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs",
1564+
1565+
// with feature
1566+
"lno1pgx9getnwss8vetrw3hhyucvp5yqqqqqqqqqqqqqqqqqqqqkyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
1567+
1568+
// with blinded path via Bob (0x424242...), blinding 020202...
1569+
"lno1pgx9getnwss8vetrw3hhyucs5ypjgef743p5fzqq9nqxh0ah7y87rzv3ud0eleps9kl2d5348hq2k8qzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgqpqqqqqqqqqqqqqqqqqqqqqqqqqqqzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqqzq3zyg3zyg3zyg3vggzamrjghtt05kvkvpcp0a79gmy3nt6jsn98ad2xs8de6sl9qmgvcvs",
1570+
1571+
// ... and with second blinded path via Carol (0x434343...), blinding 020202...
1572+
"lno1pgx9getnwss8vetrw3hhyucsl5q5yqeyv5l2cs6y3qqzesrth7mlzrlp3xg7xhulusczm04x6g6nms9trspqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqqsqqqqqqqqqqqqqqqqqqqqqqqqqqpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqpqg3zyg3zyg3zygz0uc7h32x9s0aecdhxlk075kn046aafpuuyw8f5j652t3vha2yqrsyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqzqqqqqqqqqqqqqqqqqqqqqqqqqqqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqqyzyg3zyg3zyg3zzcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese",
1573+
1574+
// unknown odd field
1575+
"lno1pgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxfppf5x2mrvdamk7unvvs",
15441576
];
15451577
for encoded_offer in &offers {
15461578
if let Err(e) = encoded_offer.parse::<Offer>() {
@@ -1550,48 +1582,155 @@ mod bech32_tests {
15501582
}
15511583

15521584
#[test]
1553-
fn fails_parsing_bech32_encoded_offers_with_invalid_continuations() {
1554-
let offers = [
1555-
// BOLT 12 test vectors
1556-
"lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+",
1557-
"lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+ ",
1558-
"+lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
1559-
"+ lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
1560-
"ln++o1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
1561-
];
1562-
for encoded_offer in &offers {
1563-
match encoded_offer.parse::<Offer>() {
1564-
Ok(_) => panic!("Valid offer: {}", encoded_offer),
1565-
Err(e) => assert_eq!(e, Bolt12ParseError::InvalidContinuation),
1566-
}
1567-
}
1585+
fn fails_parsing_bech32_encoded_offers() {
1586+
// Malformed: fields out of order
1587+
assert_eq!(
1588+
"lno1zcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszpgz5znzfgdzs".parse::<Offer>(),
1589+
Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
1590+
);
15681591

1569-
}
1592+
// Malformed: unknown even TLV type 78
1593+
assert_eq!(
1594+
"lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpysgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq".parse::<Offer>(),
1595+
Err(Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
1596+
);
15701597

1571-
#[test]
1572-
fn fails_parsing_bech32_encoded_offer_with_invalid_hrp() {
1573-
let encoded_offer = "lni1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
1574-
match encoded_offer.parse::<Offer>() {
1575-
Ok(_) => panic!("Valid offer: {}", encoded_offer),
1576-
Err(e) => assert_eq!(e, Bolt12ParseError::InvalidBech32Hrp),
1577-
}
1578-
}
1598+
// Malformed: empty
1599+
assert_eq!(
1600+
"lno1".parse::<Offer>(),
1601+
Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingDescription)),
1602+
);
15791603

1580-
#[test]
1581-
fn fails_parsing_bech32_encoded_offer_with_invalid_bech32_data() {
1582-
let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo";
1583-
match encoded_offer.parse::<Offer>() {
1584-
Ok(_) => panic!("Valid offer: {}", encoded_offer),
1585-
Err(e) => assert_eq!(e, Bolt12ParseError::Bech32(bech32::Error::InvalidChar('o'))),
1586-
}
1587-
}
1604+
// Malformed: truncated at type
1605+
assert_eq!(
1606+
"lno1pg".parse::<Offer>(),
1607+
Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
1608+
);
15881609

1589-
#[test]
1590-
fn fails_parsing_bech32_encoded_offer_with_invalid_tlv_data() {
1591-
let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxgqqqqq";
1592-
match encoded_offer.parse::<Offer>() {
1593-
Ok(_) => panic!("Valid offer: {}", encoded_offer),
1594-
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
1595-
}
1610+
// Malformed: truncated in length
1611+
assert_eq!(
1612+
"lno1pt7s".parse::<Offer>(),
1613+
Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
1614+
);
1615+
1616+
// Malformed: truncated after length
1617+
assert_eq!(
1618+
"lno1pgpq".parse::<Offer>(),
1619+
Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
1620+
);
1621+
1622+
// Malformed: truncated in description
1623+
assert_eq!(
1624+
"lno1pgpyz".parse::<Offer>(),
1625+
Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
1626+
);
1627+
1628+
// Malformed: invalid offer_chains length
1629+
assert_eq!(
1630+
"lno1qgqszzs9g9xyjs69zcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
1631+
Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
1632+
);
1633+
1634+
// Malformed: truncated currency UTF-8
1635+
assert_eq!(
1636+
"lno1qcqcqzs9g9xyjs69zcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
1637+
Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
1638+
);
1639+
1640+
// Malformed: invalid currency UTF-8
1641+
assert_eq!(
1642+
"lno1qcpgqsg2q4q5cj2rg5tzzqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqg".parse::<Offer>(),
1643+
Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
1644+
);
1645+
1646+
// Malformed: truncated description UTF-8
1647+
assert_eq!(
1648+
"lno1pgqcq93pqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqy".parse::<Offer>(),
1649+
Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
1650+
);
1651+
1652+
// Malformed: invalid description UTF-8
1653+
assert_eq!(
1654+
"lno1pgpgqsgkyypqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs".parse::<Offer>(),
1655+
Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
1656+
);
1657+
1658+
// Malformed: truncated offer_paths
1659+
assert_eq!(
1660+
"lno1pgz5znzfgdz3qqgpzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
1661+
Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
1662+
);
1663+
1664+
// Malformed: zero num_hops in blinded_path
1665+
assert_eq!(
1666+
"lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
1667+
Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
1668+
);
1669+
1670+
// Malformed: truncated onionmsg_hop in blinded_path
1671+
assert_eq!(
1672+
"lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqspqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqgkyypqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs".parse::<Offer>(),
1673+
Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
1674+
);
1675+
1676+
// Malformed: bad first_node_id in blinded_path
1677+
assert_eq!(
1678+
"lno1pgz5znzfgdz3qqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqspqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqgqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
1679+
Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
1680+
);
1681+
1682+
// Malformed: bad blinding in blinded_path
1683+
assert_eq!(
1684+
"lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcpqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqgqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
1685+
Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
1686+
);
1687+
1688+
// Malformed: bad blinded_node_id in onionmsg_hop
1689+
assert_eq!(
1690+
"lno1pgz5znzfgdz3qqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqspqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqgqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
1691+
Err(Bolt12ParseError::Decode(DecodeError::ShortRead)),
1692+
);
1693+
1694+
// Malformed: truncated issuer UTF-8
1695+
assert_eq!(
1696+
"lno1pgz5znzfgdz3yqvqzcssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsz".parse::<Offer>(),
1697+
Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
1698+
);
1699+
1700+
// Malformed: invalid issuer UTF-8
1701+
assert_eq!(
1702+
"lno1pgz5znzfgdz3yq5qgytzzqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqg".parse::<Offer>(),
1703+
Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
1704+
);
1705+
1706+
// Malformed: invalid offer_node_id
1707+
assert_eq!(
1708+
"lno1pgz5znzfgdz3vggzqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvpsxqcrqvps".parse::<Offer>(),
1709+
Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
1710+
);
1711+
1712+
// Contains type >= 80
1713+
assert_eq!(
1714+
"lno1pgz5znzfgdz3vggzqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgp9qgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqq".parse::<Offer>(),
1715+
Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)),
1716+
);
1717+
1718+
// TODO: Resolved in spec https://github.com/lightning/bolts/pull/798/files#r1334851959
1719+
// Contains unknown feature 22
1720+
assert!(
1721+
"lno1pgx9getnwss8vetrw3hhyucvqdqqqqqkyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg".parse::<Offer>().is_ok()
1722+
);
1723+
1724+
// Missing offer_description
1725+
assert_eq!(
1726+
"lno1zcss9mk8y3wkklfvevcrszlmu23kfrxh49px20665dqwmn4p72pksese".parse::<Offer>(),
1727+
Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingDescription)),
1728+
);
1729+
1730+
// Missing offer_node_id"
1731+
assert_eq!(
1732+
"lno1pgx9getnwss8vetrw3hhyuc".parse::<Offer>(),
1733+
Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSigningPubkey)),
1734+
);
15961735
}
15971736
}

lightning/src/offers/parse.rs

+90
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,93 @@ impl From<secp256k1::Error> for Bolt12ParseError {
216216
Self::InvalidSignature(error)
217217
}
218218
}
219+
220+
#[cfg(test)]
221+
mod bolt12_tests {
222+
use super::Bolt12ParseError;
223+
use crate::offers::offer::Offer;
224+
225+
#[test]
226+
fn encodes_offer_as_bech32_without_checksum() {
227+
let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
228+
let offer = dbg!(encoded_offer.parse::<Offer>().unwrap());
229+
let reencoded_offer = offer.to_string();
230+
dbg!(reencoded_offer.parse::<Offer>().unwrap());
231+
assert_eq!(reencoded_offer, encoded_offer);
232+
}
233+
234+
#[test]
235+
fn parses_bech32_encoded_offers() {
236+
let offers = [
237+
// A complete string is valid
238+
"lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
239+
240+
// + can join anywhere
241+
"l+no1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
242+
243+
// Multiple + can join
244+
"lno1pqps7sjqpgt+yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+5k7msjzfpy7nz5yqcn+ygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+5xvxg",
245+
246+
// + can be followed by whitespace
247+
"lno1pqps7sjqpgt+ yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+ 5k7msjzfpy7nz5yqcn+\nygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+\r\n 5xvxg",
248+
];
249+
for encoded_offer in &offers {
250+
if let Err(e) = encoded_offer.parse::<Offer>() {
251+
panic!("Invalid offer ({:?}): {}", e, encoded_offer);
252+
}
253+
}
254+
}
255+
256+
#[test]
257+
fn fails_parsing_bech32_encoded_offers_with_invalid_continuations() {
258+
let offers = [
259+
// + must be surrounded by bech32 characters
260+
"lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+",
261+
"lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+ ",
262+
"+lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
263+
"+ lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
264+
"ln++o1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
265+
];
266+
for encoded_offer in &offers {
267+
match encoded_offer.parse::<Offer>() {
268+
Ok(_) => panic!("Valid offer: {}", encoded_offer),
269+
Err(e) => assert_eq!(e, Bolt12ParseError::InvalidContinuation),
270+
}
271+
}
272+
}
273+
}
274+
275+
#[cfg(test)]
276+
mod tests {
277+
use super::Bolt12ParseError;
278+
use bitcoin::bech32;
279+
use crate::ln::msgs::DecodeError;
280+
use crate::offers::offer::Offer;
281+
282+
#[test]
283+
fn fails_parsing_bech32_encoded_offer_with_invalid_hrp() {
284+
let encoded_offer = "lni1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
285+
match encoded_offer.parse::<Offer>() {
286+
Ok(_) => panic!("Valid offer: {}", encoded_offer),
287+
Err(e) => assert_eq!(e, Bolt12ParseError::InvalidBech32Hrp),
288+
}
289+
}
290+
291+
#[test]
292+
fn fails_parsing_bech32_encoded_offer_with_invalid_bech32_data() {
293+
let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo";
294+
match encoded_offer.parse::<Offer>() {
295+
Ok(_) => panic!("Valid offer: {}", encoded_offer),
296+
Err(e) => assert_eq!(e, Bolt12ParseError::Bech32(bech32::Error::InvalidChar('o'))),
297+
}
298+
}
299+
300+
#[test]
301+
fn fails_parsing_bech32_encoded_offer_with_invalid_tlv_data() {
302+
let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxgqqqqq";
303+
match encoded_offer.parse::<Offer>() {
304+
Ok(_) => panic!("Valid offer: {}", encoded_offer),
305+
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
306+
}
307+
}
308+
}

0 commit comments

Comments
 (0)