@@ -9,9 +9,10 @@ use core::str::FromStr;
9
9
use std:: error;
10
10
11
11
use bech32:: primitives:: decode:: { CheckedHrpstring , CheckedHrpstringError } ;
12
- use bech32:: { Bech32 , Fe32 , Fe32IterExt } ;
12
+ use bech32:: { Fe32 , Fe32IterExt } ;
13
13
14
14
use crate :: prelude:: * ;
15
+ use crate :: Bolt11Bech32 ;
15
16
use bitcoin:: hashes:: sha256;
16
17
use bitcoin:: hashes:: Hash ;
17
18
use bitcoin:: { PubkeyHash , ScriptHash , WitnessVersion } ;
@@ -377,7 +378,7 @@ impl FromStr for SignedRawBolt11Invoice {
377
378
type Err = Bolt11ParseError ;
378
379
379
380
fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
380
- let parsed = CheckedHrpstring :: new :: < Bech32 > ( s) ?;
381
+ let parsed = CheckedHrpstring :: new :: < Bolt11Bech32 > ( s) ?;
381
382
let hrp = parsed. hrp ( ) ;
382
383
// Access original non-packed 32 byte values (as Fe32s)
383
384
// Note: the type argument is needed due to the API peculiarities, but it's not used
@@ -1175,4 +1176,244 @@ mod test {
1175
1176
)
1176
1177
)
1177
1178
}
1179
+
1180
+ // Test some long invoice test vectors successfully roundtrip. Generated
1181
+ // from Lexe proptest: <https://github.com/lexe-app/lexe-public/blob/4bc7018307e5221e1e1ee8b17ce366338fb11a16/common/src/ln/invoice.rs#L183>.
1182
+ #[ test]
1183
+ fn test_deser_long_test_vectors ( ) {
1184
+ use crate :: Bolt11Invoice ;
1185
+
1186
+ #[ track_caller]
1187
+ fn parse_ok ( invoice_str : & str ) {
1188
+ let invoice = Bolt11Invoice :: from_str ( invoice_str) . unwrap ( ) ;
1189
+ let invoice_str2 = invoice. to_string ( ) ;
1190
+ if invoice_str != invoice_str2 {
1191
+ panic ! (
1192
+ "Invoice does not roundtrip: invoice_str != invoice_str2\n \
1193
+ invoice_str: {invoice_str}\n \
1194
+ invoice_str2: {invoice_str2}\n \
1195
+ \n \
1196
+ {invoice:?}"
1197
+ ) ;
1198
+ }
1199
+ }
1200
+
1201
+ // 1024 B shrunk invoice just above previous limit of 1023 B from Lexe proptest
1202
+ parse_ok (
1203
+ "lnbc10000000000000000010p1qqqqqqqdtuxpqkzq8sjzqgps4pvyczqq8sjzqgpuysszq0pyyqsrp2zs0sjz\
1204
+ qgps4pxrcfpqyqc2slpyyqsqsv9gwz59s5zqpqyps5rc9qsrs2pqxz5ysyzcfqgysyzs0sjzqgqq8sjzqgps4p\
1205
+ xqqzps4pqpssqgzpxps5ruysszqrps4pg8p2zgpsc2snpuysszqzqsgqvys0pyyqsrcfpqyqvycv9gfqqrcfpq\
1206
+ yq7zggpq8q5zqyruysszqwpgyqxpsjqsgq7zggpqps7zggpq8sjzqgqgqq7zggpqpq7zggpq8q5zqqpuysszq0\
1207
+ pyyqsqs0pyyqspsnqgzpqpqlpyyqsqszpuysszqyzvzpvysrqq8sjzqgqvrp7zggpqpqxpsspp5mf45hs3cgph\
1208
+ h0074r5qmr74y82r26ac4pzdg4nd9mdmsvz6ffqpssp5vr4yra4pcv74h9hk3d0233nqu4gktpuykjamrafrdp\
1209
+ uedqugzh3q9q2sqqqqqysgqcqrpqqxq8pqqqqqqnp4qgvcxpme2q5lng36j9gruwlrtk2f86s3c5xmk87yhvyu\
1210
+ wdeh025q5r9yqwnqegv9hj9nzkhyxaeyq92wcrnqp36pyrc2qzrvswj5g96ey2dn6qqqqqqqqqqqqqqqqqqqqq\
1211
+ qqqqqqqqp9a5vs0t4z56p64xyma8s84yvdx7uhqj0gvrr424fea2wpztq2fwqqqqqqqqqqqqqqqqqqqqqqqqqq\
1212
+ qqqqmy9qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
1213
+ qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpcnsxc32du9n7amlypuhclzqrt6lkegq\
1214
+ 0v3r7nczjv9tv30z7phq80r3dm7pvgykl7gwuenmem93h5xwdwac6ngsmzqc34khrg3qjgsq6qk6lc"
1215
+ ) ;
1216
+ // 1517 B mainnet invoice from Lexe proptest
1217
+ parse_ok (
1218
+ "lnbc8735500635020489010p1av5kfs8deupvyk4u5ynj03hmalhhhml0fxc2jlrv9z4lg6s4hnhkz69malhhe\
1219
+ t3x9yqpsxru4a3kwar2qtu2q2ughx367q600s5x7c7tln4k0fu78skxqevaqm8sayhuur377zgf3uf94n57xzh\
1220
+ dw99u42hwc089djn5xj723w7zageflsnzdmyte89tecf2ac7xhg4y3u9f4xpuv2hwxjlsarp0e24fu8tme6rgv\
1221
+ 0tqj08z9f4u30rw59k8emhtvs7wye0xfw6x5q5tju2p208rvtkunzwtwghtp22tlnh62gxwhfkxp4cnz7ts3rx\
1222
+ vlzszhv9y00h77lpdvcjyhjtmalh5dn5e8n5w8cqle0vunzduu4nza9y0734qhxday9hzywl0aa0vhzy0qmphc\
1223
+ 64d4hduj08dv2krpgqtc2v83gptk34reelxyc7wsgnze890c6nrv6p0cmepatc269eayzjjkqk30n52rfl5dg7\
1224
+ wztl96f7wc2tzx34q909xuajnyt4u4lnk87lwal7z0etdz5tmece0v3u796jfp68nccn05ty54ncfelts3v8g0\
1225
+ sn6v6hsu87zat4r03368ersu87252dd0nswymxzc2pyxl8yy844hspuyj47w0px4u4leefq568sk0rr9th4ql9\
1226
+ f9ykawrczkz5hp22nstg3lrlsa6u2q2ull3kzce2sh0h77sjv0zszhzy4hfh6u0pwux5l3gpthsn72mfu47sw9\
1227
+ zw3hzk7srznp27z0etdp0725me00sn72mgkf0fteehruk0lg6swh34z52puaekzmjlmalhhe6m8ug7z3c8g8zh\
1228
+ jjspp5zj0sm85g5ufng9w7s6p4ucdk80tyvz64sg54v0cy4vgnr37f78sqsp5l6azu2hv6we30er90jrslqpvd\
1229
+ trnrphhesca2wg5q83k52rsu2cq9q2sqqqqqysgqcqr8h2np4qw0ha2k282hm8jh5rcfq0hsp2zhddtlc5vs23\
1230
+ uphyv0lv3k8sqsfgfp4qyrk86tx5xg2aa7et4cdzhnvl5s4nd33ugytt7gamk9tugn9yransr9yq08gpwsn8t2\
1231
+ tq4ducjfhrcz707av0ss20urjh8vldrpmehqxa0stkesvuq82txyqzfhej7qccswy7k5wvcppk63c6zpjytfda\
1232
+ ccadacjtn52lpe6s85rjfqlxzp6frq33xshaz2nr9xjkhd3jj8qg39nmfzvpgmayakqmy9rseakwgcudug7hs4\
1233
+ 5wh430ywh7qhj3khczh8gle4cn93ymgfwa7rrvcw9lywyyz58k4p40a3nu9svthaf0qeg8f2ay4tw9p48p70qm\
1234
+ ayu3ejl2q8pj9e2l22h7775tl44hs6ke4sdfgcr6aj8wra4r2v9sj6xa5chd5ctpfg8chtrer3kkp0e6af88lk\
1235
+ rfxcklf2hyslv2hr0xl5lwrm5y5uttxn4ndfz8789znf78nspa3xy68"
1236
+ ) ;
1237
+ // 1804 B regtest invoice from Lexe proptest
1238
+ parse_ok (
1239
+ "lnbcrt17124979001314909880p1y6lkcwgd76tfnxksfk2atyy4tzw4nyg6jrx3282s2ygvcxyj64gevhxsjk\
1240
+ 2ymhzv3e0p5h5u3kfey92jt9ge44gsfnwycxynm2g3unw3ntt9qh25texe98jcfhxvcxuezxw9tngwrndpy9s4\
1241
+ p4x9eyze2tfe9rxm68tp5yj5jfduen2nny8prhsm6edegn2stww4n4gwp4vfjkvdthd43524n9fa8h262vwesk\
1242
+ g66nw3vnyafn29zhsvfeg9mxummtfp35uumzfqmhy3jwgdh55mt5xpvhgmjn25uku5e5g939wmmnvdfygnrdgd\
1243
+ h56uzcx4a92vfhgdcky3z9gfnrsvp4f4f55j68vak9yufhvdm8x5zrgc6955jvf429zumv89nh2a35wae5yntg\
1244
+ v985jumpxehyv7t92pjrwufs89yh23f5ddy5s568wgchve3cg9ek5nzewgcrzjz0dftxg3nvf4hngje52ac4zm\
1245
+ esxpvk6sfef4hkuetvd4vk6n29wftrw5rvg4yy2vjjwyexc5mnvfd8xknndpqkkenx0q642j35298hwve3dyc5\
1246
+ 25jrd3295sm9v9jrqup3wpykg7zd239ns7jgtqu95jz0deaxksjh2fu56n6n2f5x6mm8wa89qjfef385sam2x9\
1247
+ mxcs20gfpnq460d3axzknnf3e4sw2kvf25wjjxddpyg52dw4vx7nn2w9cyu5t8vfnyxjtpg33kssjp24ch536p\
1248
+ d938snmtx345x6r4x93kvv2tff855um3tfekxjted4kxys2kve5hvu6g89z4ynmjgfhnw7tv892rymejgvey77\
1249
+ rcfqe9xjr92d85636fvajxyajndfa92k2nxycx5jtjx4zxsm2y2dyn2up50f5ku3nrfdk4g5npxehkzjjv8y69\
1250
+ gveev4z56denddaxy7tfwe8xx42zgf6kzmnxxpk826ze2s6xk6jrwearw6ejvd8rsvj2fpg525jtd5pp5j2tlt\
1251
+ 28m4kakjr84w6ce4fd8e7awy6ncyswcyut760rdnem30ptssp5p5u3xgxxtr6aev8y2w9m30wcw3kyn7fgm8wm\
1252
+ f8qw8wzrqt34zcvq9q2sqqqqqysgqcqypmw9xq8lllllllnp4qt36twam2ca08m3s7vnhre3c0j89589wyw4vd\
1253
+ k7fln0lryxzkdcrur28qwqq3hnyt84vsasuldd2786eysdf4dyuggwsmvw2atftf7spkmpa9dd3efq5tenpqm2\
1254
+ v7vcz2a4s0s7jnqpjn0srysnstnw5y5z9taxn0ue37aqgufxcdsj6f8a2m4pm9udppdzc4shsdqzzx0u0rm4xl\
1255
+ js0dqz3c5zqyvglda7nsqvqfztmlyup7vyuadzav4zyuqwx90ev6nmk53nkhkt0sev9e745wxqtdvrqzgqkaka\
1256
+ zen7e2qmsdauk665g3llg5qtl79t3xulrhjnducehdn72gpmkjvtth7kh6ejpl9dv0qcsxv2jvzzvg0hzdmk3y\
1257
+ jsmydqksdk3h78kc63qnr265h8vyeslqexszppfm7y287t3gxvhw0ulg2wp0rsw3tevz03z50kpy77zdz9snxm\
1258
+ kkwxd76xvj4qvj2f89rrnuvdvzw947ay0kydc077pkec2jet9qwp2tud98s24u65uz07eaxk5jk3e4nggn2caa\
1259
+ ek2p5pkrc6mm6mxjm2ezpdu8p5jstg6tgvnttgac3ygt5ys04t4udujzlshpl7e4f3ff03xe6v24cp6aq4wa"
1260
+ ) ;
1261
+ // 1870 B testnet invoice from Lexe proptest
1262
+ parse_ok (
1263
+ "lntb5826417333454665580p1c5rwh5edlhf33hvkj5vav5z3t02a5hxvj3vfv5kuny2f3yzj6zwf9hx3nn2fk\
1264
+ 9gepc2a3ywvj6dax5v3jy2d5nxmp3gaxhycjkv38hx4z4d4vyznrp2p24xa6t2pg4w4rrxfens6tcxdhxvvfhx\
1265
+ a8xvvpkgat8xnpe2p44juz9g43hyur00989gvfhwd2kj72wfum4g4mgx5m5cs2rg9d9vnn6xe89ydnnvfpyy52\
1266
+ s2dxx2er4x4xxwstdd5cxwdrjw3nkxnnv2uexxnrxw4t56sjswfn52s2xv4t8xmjtwpn8xm6sfeh4q526dyu8x\
1267
+ 3r9gceyw6fhd934qjttvdk57az5w368zdrhwfjxxu35xcmrsmmpd4g8wwtev4tkzutdd32k56mxveuy6c6v2em\
1268
+ yv7zkfp39zjpjgd8hx7n4xph5kceswf6xxmnyfcuxca20fp24z7ncvfhyu5jf2exhw36nwf68s7rh2a6yzjf4d\
1269
+ gukcenfxpchqsjn2pt5x334tf98wsm6dvcrvvfcwapxvk2cdvmk2npcfe68zue3w4f9xc6s2fvrw6nrg3fkskt\
1270
+ e2ftxyc20ffckcd692964sdzjwdp4yvrfdfm9q72pxp3kwat5f4j9xee5da8rss60w92857tgwych55f5w3n8z\
1271
+ mzexpy4jwredejrqm6txf3nxm64ffh8x460dp9yjazhw4yx6dm5xerysnn5wa455k3h2d89ss2fd9axwjp3f4r\
1272
+ 9qdmfd4fx6stx2eg9sezrv369w7nvvfvhj4nnwaz5z3ny8qcxcdnvwd64jc2nx9uy2e2gxdrnx6r3w9ykxatxx\
1273
+ g6kk6rv2ekr2emwx5ehy362d3x82dzvddfxs5rcg4vn27npf564qdtg2anycc6523jnwe3e0p65unrpvccrs5m\
1274
+ 2fuexgmnj23ay5e34v4xk5jnrwpg4xemfwqe5vjjjw9qk76zsd9yrzu6xdpv5v5ntdejxg6jtv3kx65t6gdhrg\
1275
+ vj3fe34sj2vv3h5kegpp57hjf5kv6clw97y2e063yuz0psrz9a6l49v836dflum00rh8qtn8qsp5gd29qycuze\
1276
+ 08xls8l32zjaaf2uqv78v97lg9ss0c699huw980h2q9q2sqqqqqysgqcqr8ulnp4q26hcfwr7qxz7lwwlr2kjc\
1277
+ rws7m2u5j36mm0kxa45uxy6zvsqt2zzfppjdkrm2rlgadt9dq3d6jkv4r2cugmf2kamr28qwuleyzzyyly8a6t\
1278
+ u70eldahx7hzxx5x9gms7vjjr577ps8n4qyds5nern39j0v7czkch2letnt46895jupxgehf208xgxz8d6j8gu\
1279
+ 3h2qqtsk9nr9nuquhkqjxw40h2ucpldrawmktxzxdgtkt9a3p95g98nywved8s8laj2a0c98rq5zzdnzddz6nd\
1280
+ w0lvr6u0av9m7859844cgz9vpeq05gw79zqae2s7jzeq66wydyueqtp56qc67g7krv6lj5aahxtmq4y208q5qy\
1281
+ z38cnwl9ma6m5f4nhzqaj0tjxpfrk4nr5arv9d20lvxvddvffhzygmyuvwd959uhdcgcgjejchqt2qncuwpqqk\
1282
+ 5vws7dflw8x6esrfwhz7h3jwmhevf445k76nme926sr8drsdveqg7l7t7lnjvhaludqnwk4l2pmevkjf9pla92\
1283
+ 4p77v76r7x8jzyy7h59hmk0lgzfsk6c8dpj37hssj7jt4q7jzvy8hq25l3pag37axxanjqnq56c47gpgy6frsy\
1284
+ c0str9w2aahz4h6t7axaka4cwvhwg49r6qgj8kwz2mt6vcje25l9ekvmgq5spqtn"
1285
+ ) ;
1286
+ }
1287
+
1288
+ // Generate a valid invoice of `MAX_LENGTH` bytes and ensure that it roundtrips.
1289
+ #[ test]
1290
+ fn test_serde_long_invoice ( ) {
1291
+ use crate :: TaggedField :: * ;
1292
+ use crate :: {
1293
+ Bolt11Invoice , Bolt11InvoiceFeatures , Bolt11InvoiceSignature , Currency ,
1294
+ PositiveTimestamp , RawBolt11Invoice , RawDataPart , RawHrp , RawTaggedField , Sha256 ,
1295
+ SignedRawBolt11Invoice ,
1296
+ } ;
1297
+ use bitcoin:: secp256k1:: ecdsa:: { RecoverableSignature , RecoveryId } ;
1298
+ use bitcoin:: secp256k1:: PublicKey ;
1299
+ use lightning_types:: routing:: { RouteHint , RouteHintHop , RoutingFees } ;
1300
+
1301
+ // Generate an `UnknownSemantics` field with a given length.
1302
+ fn unknown_semantics_field ( len : usize ) -> Vec < Fe32 > {
1303
+ assert ! ( len <= 1023 ) ;
1304
+ let mut field = Vec :: with_capacity ( len + 3 ) ;
1305
+ // Big-endian encoded length prefix
1306
+ field. push ( Fe32 :: Q ) ;
1307
+ field. push ( Fe32 :: try_from ( ( len >> 5 ) as u8 ) . unwrap ( ) ) ;
1308
+ field. push ( Fe32 :: try_from ( ( len & 0x1f ) as u8 ) . unwrap ( ) ) ;
1309
+ // Data
1310
+ field. extend ( std:: iter:: repeat ( Fe32 :: P ) . take ( len) ) ;
1311
+ field
1312
+ }
1313
+
1314
+ // Invoice fields
1315
+ let payment_hash = sha256:: Hash :: from_str (
1316
+ "0001020304050607080900010203040506070809000102030405060708090102" ,
1317
+ )
1318
+ . unwrap ( ) ;
1319
+ let description = std:: iter:: repeat ( "A" ) . take ( 639 ) . collect :: < String > ( ) ;
1320
+ let fallback_addr = crate :: Fallback :: SegWitProgram {
1321
+ version : bitcoin:: WitnessVersion :: V0 ,
1322
+ program : vec ! [ 0 ; 32 ] ,
1323
+ } ;
1324
+ let payee_pk = PublicKey :: from_slice ( & [
1325
+ 0x03 , 0x24 , 0x65 , 0x3e , 0xac , 0x43 , 0x44 , 0x88 , 0x00 , 0x2c , 0xc0 , 0x6b , 0xbf , 0xb7 ,
1326
+ 0xf1 , 0x0f , 0xe1 , 0x89 , 0x91 , 0xe3 , 0x5f , 0x9f , 0xe4 , 0x30 , 0x2d , 0xbe , 0xa6 , 0xd2 ,
1327
+ 0x35 , 0x3d , 0xc0 , 0xab , 0x1c ,
1328
+ ] )
1329
+ . unwrap ( ) ;
1330
+ let route_hints = std:: iter:: repeat ( RouteHintHop {
1331
+ src_node_id : payee_pk,
1332
+ short_channel_id : 0x0102030405060708 ,
1333
+ fees : RoutingFees { base_msat : 1 , proportional_millionths : 20 } ,
1334
+ cltv_expiry_delta : 3 ,
1335
+ htlc_minimum_msat : None ,
1336
+ htlc_maximum_msat : None ,
1337
+ } )
1338
+ . take ( 12 )
1339
+ . collect :: < Vec < _ > > ( ) ;
1340
+
1341
+ // Build raw invoice
1342
+ let raw_invoice = RawBolt11Invoice {
1343
+ hrp : RawHrp {
1344
+ currency : Currency :: Bitcoin ,
1345
+ raw_amount : Some ( 10000000000000000010 ) ,
1346
+ si_prefix : Some ( crate :: SiPrefix :: Pico ) ,
1347
+ } ,
1348
+ data : RawDataPart {
1349
+ timestamp : PositiveTimestamp :: from_unix_timestamp ( 1496314658 ) . unwrap ( ) ,
1350
+ tagged_fields : vec ! [
1351
+ PaymentHash ( Sha256 ( payment_hash) ) . into( ) ,
1352
+ Description ( crate :: Description :: new( description) . unwrap( ) ) . into( ) ,
1353
+ PayeePubKey ( crate :: PayeePubKey ( payee_pk) ) . into( ) ,
1354
+ ExpiryTime ( crate :: ExpiryTime ( std:: time:: Duration :: from_secs( u64 :: MAX ) ) ) . into( ) ,
1355
+ MinFinalCltvExpiryDelta ( crate :: MinFinalCltvExpiryDelta ( u64 :: MAX ) ) . into( ) ,
1356
+ Fallback ( fallback_addr) . into( ) ,
1357
+ PrivateRoute ( crate :: PrivateRoute ( RouteHint ( route_hints) ) ) . into( ) ,
1358
+ PaymentSecret ( crate :: PaymentSecret ( [ 17 ; 32 ] ) ) . into( ) ,
1359
+ PaymentMetadata ( vec![ 0x69 ; 639 ] ) . into( ) ,
1360
+ Features ( Bolt11InvoiceFeatures :: from_le_bytes( vec![ 0xaa ; 639 ] ) ) . into( ) ,
1361
+ // This invoice is 4458 B w/o unknown semantics fields.
1362
+ // Need to add some non-standard fields to reach 7089 B limit.
1363
+ RawTaggedField :: UnknownSemantics ( unknown_semantics_field( 1023 ) ) ,
1364
+ RawTaggedField :: UnknownSemantics ( unknown_semantics_field( 1023 ) ) ,
1365
+ RawTaggedField :: UnknownSemantics ( unknown_semantics_field( 576 ) ) ,
1366
+ ] ,
1367
+ } ,
1368
+ } ;
1369
+
1370
+ // Build signed invoice
1371
+ let hash = [
1372
+ 0x75 , 0x99 , 0xe1 , 0x51 , 0x7f , 0xa1 , 0x0e , 0xb5 , 0xc0 , 0x79 , 0xb4 , 0x6e , 0x8e , 0x62 ,
1373
+ 0x0c , 0x4f , 0xb0 , 0x72 , 0x71 , 0xd2 , 0x81 , 0xa1 , 0x92 , 0x65 , 0x9c , 0x90 , 0x89 , 0x69 ,
1374
+ 0xe1 , 0xf3 , 0xd6 , 0x59 ,
1375
+ ] ;
1376
+ let signature = & [
1377
+ 0x6c , 0xbe , 0xbe , 0xfe , 0xd3 , 0xfb , 0x07 , 0x68 , 0xb5 , 0x79 , 0x98 , 0x82 , 0x29 , 0xab ,
1378
+ 0x0e , 0xcc , 0x8d , 0x3a , 0x81 , 0xee , 0xee , 0x07 , 0xb3 , 0x5d , 0x64 , 0xca , 0xb4 , 0x12 ,
1379
+ 0x33 , 0x99 , 0x33 , 0x2a , 0x31 , 0xc2 , 0x2c , 0x2b , 0x62 , 0x96 , 0x4e , 0x37 , 0xd7 , 0x96 ,
1380
+ 0x50 , 0x5e , 0xdb , 0xe9 , 0xa9 , 0x5b , 0x0b , 0x3b , 0x87 , 0x22 , 0x89 , 0xed , 0x95 , 0xf1 ,
1381
+ 0xf1 , 0xdf , 0x2d , 0xb6 , 0xbd , 0xf5 , 0x0a , 0x20 ,
1382
+ ] ;
1383
+ let signature = Bolt11InvoiceSignature (
1384
+ RecoverableSignature :: from_compact ( signature, RecoveryId :: from_i32 ( 1 ) . unwrap ( ) )
1385
+ . unwrap ( ) ,
1386
+ ) ;
1387
+ let signed_invoice = SignedRawBolt11Invoice { raw_invoice, hash, signature } ;
1388
+
1389
+ // Ensure serialized invoice roundtrips
1390
+ let invoice = Bolt11Invoice :: from_signed ( signed_invoice) . unwrap ( ) ;
1391
+ let invoice_str = invoice. to_string ( ) ;
1392
+ assert_eq ! ( invoice_str. len( ) , crate :: MAX_LENGTH ) ;
1393
+ assert_eq ! ( invoice, Bolt11Invoice :: from_str( & invoice_str) . unwrap( ) ) ;
1394
+ }
1395
+
1396
+ // Test that invoices above the maximum length fail to parse with the expected error.
1397
+ #[ test]
1398
+ fn test_deser_too_long_fails ( ) {
1399
+ use crate :: { Bolt11Invoice , ParseOrSemanticError , MAX_LENGTH } ;
1400
+ use bech32:: primitives:: decode:: { CheckedHrpstringError , ChecksumError } ;
1401
+
1402
+ fn parse_is_code_length_err ( s : & str ) -> bool {
1403
+ // Need matches! b/c ChecksumError::CodeLength(_) is marked non-exhaustive
1404
+ matches ! (
1405
+ Bolt11Invoice :: from_str( s) ,
1406
+ Err ( ParseOrSemanticError :: ParseError ( Bolt11ParseError :: Bech32Error (
1407
+ CheckedHrpstringError :: Checksum ( ChecksumError :: CodeLength ( _) )
1408
+ ) ) ) ,
1409
+ )
1410
+ }
1411
+
1412
+ let mut too_long = String :: from ( "lnbc1" ) ;
1413
+ too_long. push_str (
1414
+ String :: from_utf8 ( vec ! [ b'x' ; ( MAX_LENGTH + 1 ) - too_long. len( ) ] ) . unwrap ( ) . as_str ( ) ,
1415
+ ) ;
1416
+ assert ! ( parse_is_code_length_err( & too_long) ) ;
1417
+ assert ! ( !parse_is_code_length_err( & too_long[ ..too_long. len( ) - 1 ] ) ) ;
1418
+ }
1178
1419
}
0 commit comments