github.com/decred/dcrlnd@v0.7.6/zpay32/invoice_test.go (about) 1 package zpay32 2 3 // We use package `zpay32` rather than `zpay32_test` in order to share test data 4 // with the internal tests. 5 6 import ( 7 "bytes" 8 "encoding/hex" 9 "fmt" 10 "reflect" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/decred/dcrd/chaincfg/chainhash" 16 "github.com/decred/dcrd/chaincfg/v3" 17 "github.com/decred/dcrd/dcrec/secp256k1/v4" 18 "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" 19 "github.com/decred/dcrd/txscript/v4/stdaddr" 20 "github.com/decred/dcrlnd/lnwire" 21 ) 22 23 var ( 24 testMilliAt24DCR = lnwire.MilliAtom(2400000000000) 25 testMilliAt2500uDCR = lnwire.MilliAtom(250000000) 26 testMilliAt25mDCR = lnwire.MilliAtom(2500000000) 27 testMilliAt20mDCR = lnwire.MilliAtom(2000000000) 28 29 testPaymentHash = [32]byte{ 30 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 31 0x08, 0x09, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 32 0x06, 0x07, 0x08, 0x09, 0x00, 0x01, 0x02, 0x03, 33 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x01, 0x02, 34 } 35 36 testPaymentAddr = [32]byte{ 37 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x01, 0x02, 38 0x06, 0x07, 0x08, 0x09, 0x00, 0x01, 0x02, 0x03, 39 0x08, 0x09, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 40 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 41 } 42 43 specPaymentAddr = [32]byte{ 44 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 45 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 46 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 47 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 48 } 49 50 testEmptyString = "" 51 testCupOfCoffee = "1 cup coffee" 52 testCoffeeBeans = "coffee beans" 53 testCupOfNonsense = "ナンセンス 1杯" 54 testPleaseConsider = "Please consider supporting this project" 55 56 testPrivKeyBytes, _ = hex.DecodeString("e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734") 57 testPrivKey = secp256k1.PrivKeyFromBytes(testPrivKeyBytes) 58 testPubKey = testPrivKey.PubKey() 59 60 testDescriptionHashSlice = chainhash.HashB([]byte("One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) 61 62 testExpiry0 = time.Duration(0) * time.Second 63 testExpiry60 = time.Duration(60) * time.Second 64 65 testAddrTestnet, _ = stdaddr.DecodeAddress("TsR28UZRprhgQQhzWns2M6cAwchrNVvbYq2", chaincfg.TestNet3Params()) 66 testRustyAddr, _ = stdaddr.DecodeAddress("DsQxuVRvS4eaJ42dhQEsCXauMWjvopWgrVg", chaincfg.MainNetParams()) 67 testAddrMainnetP2SH, _ = stdaddr.DecodeAddress("DcXTb4QtmnyRsnzUVViYQawqFE5PuYTdX2C", chaincfg.MainNetParams()) 68 69 testHopHintPubkeyBytes1, _ = hex.DecodeString("029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255") 70 testHopHintPubkey1, _ = secp256k1.ParsePubKey(testHopHintPubkeyBytes1) 71 testHopHintPubkeyBytes2, _ = hex.DecodeString("039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255") 72 testHopHintPubkey2, _ = secp256k1.ParsePubKey(testHopHintPubkeyBytes2) 73 74 testSingleHop = []HopHint{ 75 { 76 NodeID: testHopHintPubkey1, 77 ChannelID: 0x0102030405060708, 78 FeeBaseMAtoms: 0, 79 FeeProportionalMillionths: 20, 80 CLTVExpiryDelta: 3, 81 }, 82 } 83 testDoubleHop = []HopHint{ 84 { 85 NodeID: testHopHintPubkey1, 86 ChannelID: 0x0102030405060708, 87 FeeBaseMAtoms: 1, 88 FeeProportionalMillionths: 20, 89 CLTVExpiryDelta: 3, 90 }, 91 { 92 NodeID: testHopHintPubkey2, 93 ChannelID: 0x030405060708090a, 94 FeeBaseMAtoms: 2, 95 FeeProportionalMillionths: 30, 96 CLTVExpiryDelta: 4, 97 }, 98 } 99 100 testMessageSigner = MessageSigner{ 101 SignCompact: func(msg []byte) ([]byte, error) { 102 hash := chainhash.HashB(msg) 103 return ecdsa.SignCompact(testPrivKey, hash, true), nil 104 }, 105 } 106 107 emptyFeatures = lnwire.NewFeatureVector(nil, lnwire.Features) 108 109 // Must be initialized in init(). 110 testDescriptionHash [32]byte 111 ) 112 113 func init() { 114 copy(testDescriptionHash[:], testDescriptionHashSlice) 115 } 116 117 // TestDecodeEncode tests that an encoded invoice gets decoded into the expected 118 // Invoice object, and that reencoding the decoded invoice gets us back to the 119 // original encoded string. 120 func TestDecodeEncode(t *testing.T) { 121 t.Parallel() 122 123 tests := []struct { 124 encodedInvoice string 125 valid bool 126 decodedInvoice func() *Invoice 127 skipEncoding bool 128 beforeEncoding func(*Invoice) 129 }{ 130 { 131 encodedInvoice: "asdsaddnasdnas", // no hrp 132 valid: false, 133 }, 134 { 135 encodedInvoice: "lndcr1abcde", // too short 136 valid: false, 137 }, 138 { 139 encodedInvoice: "1asdsaddnv4wudz", // empty hrp 140 valid: false, 141 }, 142 { 143 encodedInvoice: "ln1asdsaddnv4wudz", // hrp too short 144 valid: false, 145 }, 146 { 147 encodedInvoice: "llts1dasdajtkfl6", // no "ln" prefix 148 valid: false, 149 }, 150 { 151 encodedInvoice: "lnts1dasdapukz0w", // invalid segwit prefix 152 valid: false, 153 }, 154 { 155 encodedInvoice: "lndcrm1aaamcu25m", // invalid amount 156 valid: false, 157 }, 158 { 159 encodedInvoice: "lndcr1000000000m1", // invalid amount 160 valid: false, 161 }, 162 { 163 encodedInvoice: "lndcr20m1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfqqepvrhrm9s57hejg0p662ur5j5cr03890fa7k2pypgttmh4897d3raaq85a293e9jpuqwl0rnfuwzam7yr8e690nd2ypcq9hlkdwdvycqjhlqg5", // empty fallback address field 164 valid: false, 165 }, 166 { 167 encodedInvoice: "lndcr20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3qjmp7lwpagxun9pygexvgpjdc4jdj85frqg00000000j9n4evl6mr5aj9f58zp6fyjzup6ywn3x6sk8akg5v4tgn2q8g4fhx05wf6juaxu9760yp46454gpg5mtzgerlzezqcqvjnhjh8z3g2qqsj5cgu", // invalid routing info length: not a multiple of 51 168 valid: false, 169 }, 170 { 171 // no payment hash set 172 encodedInvoice: "lndcr20m1pvjluezhp5p0y6smqsu95wrj2v9dzntwn88pmz4ck92063nkhxju832w0tr5hsqdnpkqmwa5g78xlhq029a2238kjf9klaes6pc6qvgasvvz729r0zjurxzvj5ssr2ypjs9af6qdfhkdrwwf0urkhh8p8lx4p65jq0tkcpfdajj0", 173 valid: false, 174 decodedInvoice: func() *Invoice { 175 return &Invoice{ 176 Net: chaincfg.MainNetParams(), 177 MilliAt: &testMilliAt20mDCR, 178 Timestamp: time.Unix(1496314658, 0), 179 DescriptionHash: &testDescriptionHash, 180 Destination: testPubKey, 181 Features: emptyFeatures, 182 } 183 }, 184 }, 185 { 186 // Both Description and DescriptionHash set. 187 encodedInvoice: "lndcr20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaqhp5p0y6smqsu95wrj2v9dzntwn88pmz4ck92063nkhxju832w0tr5hs0pwljxh5kezjcylatfknd2wgpc5kyayf8wntsjsaxhyw3cw0a6s9ue5y4lkeja470cldvwx075d2s06acaphjsnc4mq74nzcu0lcr0qqkady8f", 188 valid: false, 189 decodedInvoice: func() *Invoice { 190 return &Invoice{ 191 Net: chaincfg.MainNetParams(), 192 MilliAt: &testMilliAt20mDCR, 193 Timestamp: time.Unix(1496314658, 0), 194 PaymentHash: &testPaymentHash, 195 Description: &testPleaseConsider, 196 DescriptionHash: &testDescriptionHash, 197 Destination: testPubKey, 198 Features: emptyFeatures, 199 } 200 }, 201 }, 202 { 203 // Neither Description nor DescriptionHash set. 204 encodedInvoice: "lndcr20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypq8puwce8w28d8elye424a7y7l845llxkwtku8sjpf2c42ltqd7vehefdhpr0zgq4dxjl36savrdn0perhuu2n9dxhd30suv24rltkdtcpgx6lc2", 205 valid: false, 206 decodedInvoice: func() *Invoice { 207 return &Invoice{ 208 Net: chaincfg.MainNetParams(), 209 MilliAt: &testMilliAt20mDCR, 210 Timestamp: time.Unix(1496314658, 0), 211 PaymentHash: &testPaymentHash, 212 Destination: testPubKey, 213 Features: emptyFeatures, 214 } 215 }, 216 }, 217 { 218 // Has a few unknown fields, should just be ignored. 219 encodedInvoice: "lndcr20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaqtq2v93xxer9vczq8v93xxeqy2e8qjln3aelvnu077437ta5l3c6eq2ag7ervsa6l24kaanjgusy8984984ykpv73jent2c2c0zfj6sjrfraz52dq4f77hup0azr0cgpak5p5f", 220 valid: true, 221 decodedInvoice: func() *Invoice { 222 return &Invoice{ 223 Net: chaincfg.MainNetParams(), 224 MilliAt: &testMilliAt20mDCR, 225 Timestamp: time.Unix(1496314658, 0), 226 PaymentHash: &testPaymentHash, 227 Description: &testPleaseConsider, 228 Destination: testPubKey, 229 Features: emptyFeatures, 230 } 231 }, 232 skipEncoding: true, // Skip encoding since we don't have the unknown fields to encode. 233 }, 234 { 235 // Ignore unknown witness version in fallback address. 236 encodedInvoice: "lndcr20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp5p0y6smqsu95wrj2v9dzntwn88pmz4ck92063nkhxju832w0tr5hsfpzpw508d6qejxtdg4y5r3zarvary0c5xw7kqt00sjdsfuzdw87n0rlvqw0h6ul6m66s38cr3wfh3epe7e3qn8e88wjdu2n39aalhnxtpx8faczr8g4uhktkkedms76qcs0tuqaju2egppmk8nq", 237 valid: true, 238 decodedInvoice: func() *Invoice { 239 return &Invoice{ 240 Net: chaincfg.MainNetParams(), 241 MilliAt: &testMilliAt20mDCR, 242 Timestamp: time.Unix(1496314658, 0), 243 PaymentHash: &testPaymentHash, 244 DescriptionHash: &testDescriptionHash, 245 Destination: testPubKey, 246 Features: emptyFeatures, 247 } 248 }, 249 skipEncoding: true, // Skip encoding since we don't have the unknown fields to encode. 250 }, 251 { 252 // Ignore fields with unknown lengths. 253 encodedInvoice: "lndcr241pveeq09pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqpp3qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqhp5p0y6smqsu95wrj2v9dzntwn88pmz4ck92063nkhxju832w0tr5hshp38yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66np3q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfkq0wl2r2kwnhqc7mm8wmseekc2wj0rsj9nssxprqwerqfgx9lau5samrfeenge47pfjlectk4yj9axr42jpcl53e43wdlxln6amlmmcpat04z9", 254 valid: true, 255 decodedInvoice: func() *Invoice { 256 return &Invoice{ 257 Net: chaincfg.MainNetParams(), 258 MilliAt: &testMilliAt24DCR, 259 Timestamp: time.Unix(1503429093, 0), 260 PaymentHash: &testPaymentHash, 261 Destination: testPubKey, 262 DescriptionHash: &testDescriptionHash, 263 Features: emptyFeatures, 264 } 265 }, 266 skipEncoding: true, // Skip encoding since we don't have the unknown fields to encode. 267 }, 268 { 269 // Invoice with no amount. 270 encodedInvoice: "lndcr1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsmvp0ygkvzd3zh9wkfj59cuze0se5fzuh4f7rysdukv68n6fafa45sudrzg8d33paaw50zczd5mzmppqaalvzneu0yd3zfrvzhnfzpkgppyrza2", 271 valid: true, 272 decodedInvoice: func() *Invoice { 273 return &Invoice{ 274 Net: chaincfg.MainNetParams(), 275 Timestamp: time.Unix(1496314658, 0), 276 PaymentHash: &testPaymentHash, 277 Description: &testCupOfCoffee, 278 Destination: testPubKey, 279 Features: emptyFeatures, 280 } 281 }, 282 beforeEncoding: func(i *Invoice) { 283 // Since this destination pubkey was recovered 284 // from the signature, we must set it nil before 285 // encoding to get back the same invoice string. 286 i.Destination = nil 287 }, 288 }, 289 { 290 // Please make a donation of any amount using rhash 0001020304050607080900010203040506070809000102030405060708090102 to me @03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad 291 encodedInvoice: "lndcr1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq708hahqy65t7nzxer9t26yxkxxmh84rc7u7hfv2wxrjjkf5v8wqz3tf472p8dagx7u2fayqzfp8ycekwmfmacz5g83tunacfzw48m0sq4jcr74", 292 valid: true, 293 decodedInvoice: func() *Invoice { 294 return &Invoice{ 295 Net: chaincfg.MainNetParams(), 296 Timestamp: time.Unix(1496314658, 0), 297 PaymentHash: &testPaymentHash, 298 Description: &testPleaseConsider, 299 Destination: testPubKey, 300 Features: emptyFeatures, 301 } 302 }, 303 beforeEncoding: func(i *Invoice) { 304 // Since this destination pubkey was recovered 305 // from the signature, we must set it nil before 306 // encoding to get back the same invoice string. 307 i.Destination = nil 308 }, 309 }, 310 { 311 // Same as above, pubkey set in 'n' field. 312 encodedInvoice: "lndcr241pveeq09pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66husxpmqj9fh878hrkccqzvazqk2mhj0fdtjyngvhz5vje86eh39zu8cmp7k0kml38p3d3ujyuuhqe32kfgdt98t5e8r74xmwk53u5mqqm45579", 313 valid: true, 314 decodedInvoice: func() *Invoice { 315 return &Invoice{ 316 Net: chaincfg.MainNetParams(), 317 MilliAt: &testMilliAt24DCR, 318 Timestamp: time.Unix(1503429093, 0), 319 PaymentHash: &testPaymentHash, 320 Destination: testPubKey, 321 Description: &testEmptyString, 322 Features: emptyFeatures, 323 } 324 }, 325 }, 326 { 327 // Please send $3 for a cup of coffee to the same peer, within 1 minute 328 encodedInvoice: "lndcr2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpumkqh4xxuwhghf0dy5mglqnnttyg46a3ursmwv33dlwvmvkt9d8z9k7h4nhm0uun3a8hly8e92hd926j0tm0afrnzqeyapnlqhrx6cugphffhw0", 329 valid: true, 330 decodedInvoice: func() *Invoice { 331 i, _ := NewInvoice( 332 chaincfg.MainNetParams(), 333 testPaymentHash, 334 time.Unix(1496314658, 0), 335 Amount(testMilliAt2500uDCR), 336 Description(testCupOfCoffee), 337 Destination(testPubKey), 338 Expiry(testExpiry60)) 339 return i 340 }, 341 beforeEncoding: func(i *Invoice) { 342 // Since this destination pubkey was recovered 343 // from the signature, we must set it nil before 344 // encoding to get back the same invoice string. 345 i.Destination = nil 346 }, 347 }, 348 { 349 // Please send 0.0025 DCR for a cup of nonsense (ナンセンス 1杯) to the same peer, within 1 minute 350 encodedInvoice: "lndcr2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpu20gghk3mf6c570cuquvdxd0p0wym6pdcmcdmpnnhjs557vvxzvprplpwrzxef7a6emdtkv5vjeqg5mk4ea55u4wt0qk6pp0gc67w4zgq7xl5pn", 351 valid: true, 352 decodedInvoice: func() *Invoice { 353 i, _ := NewInvoice( 354 chaincfg.MainNetParams(), 355 testPaymentHash, 356 time.Unix(1496314658, 0), 357 Amount(testMilliAt2500uDCR), 358 Description(testCupOfNonsense), 359 Destination(testPubKey), 360 Expiry(testExpiry60)) 361 return i 362 }, 363 beforeEncoding: func(i *Invoice) { 364 // Since this destination pubkey was recovered 365 // from the signature, we must set it nil before 366 // encoding to get back the same invoice string. 367 i.Destination = nil 368 }, 369 }, 370 { 371 // Now send $24 for an entire list of things (hashed) 372 encodedInvoice: "lndcr20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp5p0y6smqsu95wrj2v9dzntwn88pmz4ck92063nkhxju832w0tr5hs3uzs6up5wjzjy7pl352h0rd0tujcwrvej3035gs59x2funkpx44z7r3ku04xf8xgvlxrc4dhaut5t9yxvwv2kvdge6g25zk6p87550qp0c2rnh", 373 valid: true, 374 decodedInvoice: func() *Invoice { 375 return &Invoice{ 376 Net: chaincfg.MainNetParams(), 377 MilliAt: &testMilliAt20mDCR, 378 Timestamp: time.Unix(1496314658, 0), 379 PaymentHash: &testPaymentHash, 380 DescriptionHash: &testDescriptionHash, 381 Destination: testPubKey, 382 Features: emptyFeatures, 383 } 384 }, 385 beforeEncoding: func(i *Invoice) { 386 // Since this destination pubkey was recovered 387 // from the signature, we must set it nil before 388 // encoding to get back the same invoice string. 389 i.Destination = nil 390 }, 391 }, 392 { 393 // The same, on testnet, with a fallback address TsR28UZRprhgQQhzWns2M6cAwchrNVvbYq2 394 encodedInvoice: "lntdcr20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp5p0y6smqsu95wrj2v9dzntwn88pmz4ck92063nkhxju832w0tr5hsfpp3qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpf094cd5v782dw6hu4uu2nadncanzy8emn3xzmp77n0nnzyrwzzxuel7sqyzgmrpvl4p3hncrztujznemavdwy38sa9wdmlrnzcdlscqytjln6", 395 valid: true, 396 decodedInvoice: func() *Invoice { 397 return &Invoice{ 398 Net: chaincfg.TestNet3Params(), 399 MilliAt: &testMilliAt20mDCR, 400 Timestamp: time.Unix(1496314658, 0), 401 PaymentHash: &testPaymentHash, 402 DescriptionHash: &testDescriptionHash, 403 Destination: testPubKey, 404 FallbackAddr: testAddrTestnet, 405 Features: emptyFeatures, 406 } 407 }, 408 beforeEncoding: func(i *Invoice) { 409 // Since this destination pubkey was recovered 410 // from the signature, we must set it nil before 411 // encoding to get back the same invoice string. 412 i.Destination = nil 413 }, 414 }, 415 { 416 // On mainnet, with fallback address DsQxuVRvS4eaJ42dhQEsCXauMWjvopWgrVg with extra routing info to get to node 029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255 417 encodedInvoice: "lndcr20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp5p0y6smqsu95wrj2v9dzntwn88pmz4ck92063nkhxju832w0tr5hsfpp3qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvchhyegdla6jsqjquef6f7k9m7gfj3kze2rmqphv24tcsr0v3geqk6w4mgzmup6040rvy9gy0jxlwwvqfv2ua0ycggkammcquq57y4wcqpyayvm", 418 valid: true, 419 decodedInvoice: func() *Invoice { 420 return &Invoice{ 421 Net: chaincfg.MainNetParams(), 422 MilliAt: &testMilliAt20mDCR, 423 Timestamp: time.Unix(1496314658, 0), 424 PaymentHash: &testPaymentHash, 425 DescriptionHash: &testDescriptionHash, 426 Destination: testPubKey, 427 FallbackAddr: testRustyAddr, 428 RouteHints: [][]HopHint{testSingleHop}, 429 Features: emptyFeatures, 430 } 431 }, 432 beforeEncoding: func(i *Invoice) { 433 // Since this destination pubkey was recovered 434 // from the signature, we must set it nil before 435 // encoding to get back the same invoice string. 436 i.Destination = nil 437 }, 438 }, 439 { 440 // On mainnet, with fallback address DsQxuVRvS4eaJ42dhQEsCXauMWjvopWgrVg with extra routing info to go via nodes 029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255 then 039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255 441 encodedInvoice: "lndcr20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp5p0y6smqsu95wrj2v9dzntwn88pmz4ck92063nkhxju832w0tr5hsfpp3qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqr9yq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4grqszsvpcgpy9qqqqqqgqqqqq7qqzqykl3fr9qy3yxam6xh55lxtfcp7uxsdl4krv6206de6j4lvfdu0l4hjwsy9aad8ap527ygzpc0gcrx8t98gxn3kr2xaq2nympn0jv9rqpqjas5d", 442 valid: true, 443 decodedInvoice: func() *Invoice { 444 return &Invoice{ 445 Net: chaincfg.MainNetParams(), 446 MilliAt: &testMilliAt20mDCR, 447 Timestamp: time.Unix(1496314658, 0), 448 PaymentHash: &testPaymentHash, 449 DescriptionHash: &testDescriptionHash, 450 Destination: testPubKey, 451 FallbackAddr: testRustyAddr, 452 RouteHints: [][]HopHint{testDoubleHop}, 453 Features: emptyFeatures, 454 } 455 }, 456 beforeEncoding: func(i *Invoice) { 457 // Since this destination pubkey was recovered 458 // from the signature, we must set it nil before 459 // encoding to get back the same invoice string. 460 i.Destination = nil 461 }, 462 }, 463 { 464 // On mainnet, with fallback (p2sh) address DcXTb4QtmnyRsnzUVViYQawqFE5PuYTdX2C 465 encodedInvoice: "lndcr20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp5p0y6smqsu95wrj2v9dzntwn88pmz4ck92063nkhxju832w0tr5hsfppjqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3wf2zppfn42k4t02y02rrlqner5zkg90qhy9km6m7pkd2euet2dsuqxr4qjgwns45pjhrc4vauau0f05576du50ahs87c7pvt4hm2mcq3gfmam", 466 valid: true, 467 decodedInvoice: func() *Invoice { 468 return &Invoice{ 469 Net: chaincfg.MainNetParams(), 470 MilliAt: &testMilliAt20mDCR, 471 Timestamp: time.Unix(1496314658, 0), 472 PaymentHash: &testPaymentHash, 473 DescriptionHash: &testDescriptionHash, 474 Destination: testPubKey, 475 FallbackAddr: testAddrMainnetP2SH, 476 Features: emptyFeatures, 477 } 478 }, 479 beforeEncoding: func(i *Invoice) { 480 // Since this destination pubkey was recovered 481 // from the signature, we must set it nil before 482 // encoding to get back the same invoice string. 483 i.Destination = nil 484 }, 485 }, 486 { 487 // On mainnet, please send $30 coffee beans supporting 488 // features 9, 15 and 99, using secret 0x11... 489 encodedInvoice: "lndcr25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q5sqqqqqqqqqqqqqqqpqsqe7rvhqg67n4e5kemqe9mknrt3hjvqfqkzge5k78qgfrltt202wnkvusx44ulvm7z0s9u80p3xj3s8g25r0qszmc78v8ywwkf76ly2hsp0k62xw", 490 valid: true, 491 decodedInvoice: func() *Invoice { 492 return &Invoice{ 493 Net: chaincfg.MainNetParams(), 494 MilliAt: &testMilliAt25mDCR, 495 Timestamp: time.Unix(1496314658, 0), 496 PaymentHash: &testPaymentHash, 497 PaymentAddr: &specPaymentAddr, 498 Description: &testCoffeeBeans, 499 Destination: testPubKey, 500 Features: lnwire.NewFeatureVector( 501 lnwire.NewRawFeatureVector(9, 15, 99), 502 lnwire.Features, 503 ), 504 } 505 }, 506 beforeEncoding: func(i *Invoice) { 507 // Since this destination pubkey was recovered 508 // from the signature, we must set it nil before 509 // encoding to get back the same invoice string. 510 i.Destination = nil 511 }, 512 }, 513 { 514 // On mainnet, please send $30 coffee beans supporting 515 // features 9, 15, 99, and 100, using secret 0x11... 516 encodedInvoice: "lndcr25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q4psqqqqqqqqqqqqqqqpqsqh82n8qrqmvqkkqjajnv0xgrlfhf02u7j7872t00narrlf33e60cx9dctzd59v7smeuykdtxg6sx2z5a2k0qyakyjhneysc37ghfs6hgqrrjdpu", 517 valid: true, 518 decodedInvoice: func() *Invoice { 519 return &Invoice{ 520 Net: chaincfg.MainNetParams(), 521 MilliAt: &testMilliAt25mDCR, 522 Timestamp: time.Unix(1496314658, 0), 523 PaymentHash: &testPaymentHash, 524 PaymentAddr: &specPaymentAddr, 525 Description: &testCoffeeBeans, 526 Destination: testPubKey, 527 Features: lnwire.NewFeatureVector( 528 lnwire.NewRawFeatureVector(9, 15, 99, 100), 529 lnwire.Features, 530 ), 531 } 532 }, 533 beforeEncoding: func(i *Invoice) { 534 // Since this destination pubkey was recovered 535 // from the signature, we must set it nil before 536 // encoding to get back the same invoice string. 537 i.Destination = nil 538 }, 539 }, 540 { 541 // Send 2500uDCR for a cup of coffee with a custom CLTV 542 // expiry value. 543 encodedInvoice: "lndcr2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jscqzysnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66fxyjhqjk2p5tfgzh8ddfkc7s25nc0qefzt86v7ct6rccv6577f753juhjra927ma6cg05wpd6a2tqjgc56ttp5xygrk237apy5e5vgqq660qqy", 544 valid: true, 545 decodedInvoice: func() *Invoice { 546 i, _ := NewInvoice( 547 chaincfg.MainNetParams(), 548 testPaymentHash, 549 time.Unix(1496314658, 0), 550 Amount(testMilliAt2500uDCR), 551 Description(testCupOfCoffee), 552 Destination(testPubKey), 553 CLTVExpiry(144), 554 ) 555 556 return i 557 }, 558 }, 559 { 560 // Send 2500uDCR for a cup of coffee with a payment 561 // address. 562 encodedInvoice: "lndcr2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66sp5qszsvpcgpyqsyps8pqysqqgzqvyqjqqpqgpsgpgqqypqxpq9qcrsaug6v235r6p6pmyv4gkk8rddnjwryap5vfgl843425p9ng8lttvxr2rvk7690qjpy60qvu6d32l4f06uezh6ahywuh3cu0p0wyrmeksptmu700", 563 valid: true, 564 decodedInvoice: func() *Invoice { 565 i, _ := NewInvoice( 566 chaincfg.MainNetParams(), 567 testPaymentHash, 568 time.Unix(1496314658, 0), 569 Amount(testMilliAt2500uDCR), 570 Description(testCupOfCoffee), 571 Destination(testPubKey), 572 PaymentAddr(testPaymentAddr), 573 ) 574 575 return i 576 }, 577 }, 578 { 579 // Decode a mainnet invoice while expecting active net to be testnet 580 encodedInvoice: "lndcr241pveeq09pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66husxpmqj9fh878hrkccqzvazqk2mhj0fdtjyngvhz5vje86eh39zu8cmp7k0kml38p3d3ujyuuhqe32kfgdt98t5e8r74xmwk53u5mqqm45579", 581 valid: false, 582 decodedInvoice: func() *Invoice { 583 return &Invoice{ 584 Net: chaincfg.TestNet3Params(), 585 MilliAt: &testMilliAt24DCR, 586 Timestamp: time.Unix(1503429093, 0), 587 PaymentHash: &testPaymentHash, 588 Destination: testPubKey, 589 Description: &testEmptyString, 590 Features: emptyFeatures, 591 } 592 }, 593 skipEncoding: true, // Skip encoding since we were given the wrong net 594 }, 595 } 596 597 for i, test := range tests { 598 var decodedInvoice *Invoice 599 net := chaincfg.MainNetParams() 600 if test.decodedInvoice != nil { 601 decodedInvoice = test.decodedInvoice() 602 net = decodedInvoice.Net 603 } 604 605 invoice, err := Decode(test.encodedInvoice, net) 606 if (err == nil) != test.valid { 607 t.Errorf("Decoding test %d failed: %v", i, err) 608 return 609 } 610 611 if test.valid { 612 if err := compareInvoices(decodedInvoice, invoice); err != nil { 613 t.Errorf("Invoice decoding result %d not as expected: %v", i, err) 614 return 615 } 616 } 617 618 if test.skipEncoding { 619 continue 620 } 621 622 if test.beforeEncoding != nil { 623 test.beforeEncoding(decodedInvoice) 624 } 625 626 if decodedInvoice != nil { 627 reencoded, err := decodedInvoice.Encode( 628 testMessageSigner, 629 ) 630 if (err == nil) != test.valid { 631 t.Errorf("Encoding test %d failed: %v", i, err) 632 return 633 } 634 635 if test.valid && test.encodedInvoice != reencoded { 636 t.Errorf("Encoding %d failed, expected %v, got %v", 637 i, test.encodedInvoice, reencoded) 638 return 639 } 640 } 641 } 642 } 643 644 // TestNewInvoice tests that providing the optional arguments to the NewInvoice 645 // method creates an Invoice that encodes to the expected string. 646 func TestNewInvoice(t *testing.T) { 647 t.Parallel() 648 649 tests := []struct { 650 newInvoice func() (*Invoice, error) 651 encodedInvoice string 652 valid bool 653 }{ 654 { 655 // Both Description and DescriptionHash set. 656 newInvoice: func() (*Invoice, error) { 657 return NewInvoice(chaincfg.MainNetParams(), 658 testPaymentHash, time.Unix(1496314658, 0), 659 DescriptionHash(testDescriptionHash), 660 Description(testPleaseConsider)) 661 }, 662 valid: false, // Both Description and DescriptionHash set. 663 }, 664 { 665 // Invoice with no amount. 666 newInvoice: func() (*Invoice, error) { 667 return NewInvoice( 668 chaincfg.MainNetParams(), 669 testPaymentHash, 670 time.Unix(1496314658, 0), 671 Description(testCupOfCoffee), 672 ) 673 }, 674 valid: true, 675 encodedInvoice: "lndcr1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsmvp0ygkvzd3zh9wkfj59cuze0se5fzuh4f7rysdukv68n6fafa45sudrzg8d33paaw50zczd5mzmppqaalvzneu0yd3zfrvzhnfzpkgppyrza2", 676 }, 677 { 678 // 'n' field set. 679 newInvoice: func() (*Invoice, error) { 680 return NewInvoice(chaincfg.MainNetParams(), 681 testPaymentHash, time.Unix(1503429093, 0), 682 Amount(testMilliAt24DCR), 683 Description(testEmptyString), 684 Destination(testPubKey)) 685 }, 686 valid: true, 687 encodedInvoice: "lndcr241pveeq09pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66husxpmqj9fh878hrkccqzvazqk2mhj0fdtjyngvhz5vje86eh39zu8cmp7k0kml38p3d3ujyuuhqe32kfgdt98t5e8r74xmwk53u5mqqm45579", 688 }, 689 { 690 // On mainnet, with fallback address DsQxuVRvS4eaJ42dhQEsCXauMWjvopWgrVg with extra routing info to go via nodes 029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255 then 039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255 691 newInvoice: func() (*Invoice, error) { 692 return NewInvoice(chaincfg.MainNetParams(), 693 testPaymentHash, time.Unix(1496314658, 0), 694 Amount(testMilliAt20mDCR), 695 DescriptionHash(testDescriptionHash), 696 FallbackAddr(testRustyAddr), 697 RouteHint(testDoubleHop), 698 ) 699 }, 700 valid: true, 701 encodedInvoice: "lndcr20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp5p0y6smqsu95wrj2v9dzntwn88pmz4ck92063nkhxju832w0tr5hsfpp3qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqr9yq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4grqszsvpcgpy9qqqqqqgqqqqq7qqzqykl3fr9qy3yxam6xh55lxtfcp7uxsdl4krv6206de6j4lvfdu0l4hjwsy9aad8ap527ygzpc0gcrx8t98gxn3kr2xaq2nympn0jv9rqpqjas5d", 702 }, 703 { 704 // On simnet 705 newInvoice: func() (*Invoice, error) { 706 return NewInvoice(chaincfg.SimNetParams(), 707 testPaymentHash, time.Unix(1496314658, 0), 708 Amount(testMilliAt24DCR), 709 Description(testEmptyString), 710 Destination(testPubKey)) 711 }, 712 valid: true, 713 encodedInvoice: "lnsdcr241pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66zh5xhvtchse36pt88lj4djy8g58lx26xfz3np7humcd9594rmgv92nws6vllf9mhq670x9nrwhjzw0shsklr6gq235whh9x9089ue7gpjur6cc", 714 }, 715 { 716 // On regtest 717 newInvoice: func() (*Invoice, error) { 718 return NewInvoice(chaincfg.RegNetParams(), 719 testPaymentHash, time.Unix(1496314658, 0), 720 Amount(testMilliAt24DCR), 721 Description(testEmptyString), 722 Destination(testPubKey)) 723 }, 724 valid: true, 725 encodedInvoice: "lnrdcr241pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv668eyx3hxz79l45wdm93chts7yvd7n5dd4peq0dwdkrdamnrylws34pynkyyw7dndfy047tcelp4l8w26j8jjht8urq204g3ca6tgm7ycpq5qkd2", 726 }, 727 } 728 729 for i, test := range tests { 730 731 invoice, err := test.newInvoice() 732 if err != nil && !test.valid { 733 continue 734 } 735 encoded, err := invoice.Encode(testMessageSigner) 736 if (err == nil) != test.valid { 737 t.Errorf("NewInvoice test %d failed: %v", i, err) 738 return 739 } 740 741 if test.valid && test.encodedInvoice != encoded { 742 t.Errorf("Encoding %d failed, expected %v, got %v", 743 i, test.encodedInvoice, encoded) 744 return 745 } 746 } 747 } 748 749 // TestMaxInvoiceLength tests that attempting to decode an invoice greater than 750 // maxInvoiceLength fails with ErrInvoiceTooLarge. 751 func TestMaxInvoiceLength(t *testing.T) { 752 t.Parallel() 753 754 tests := []struct { 755 encodedInvoice string 756 expectedError error 757 }{ 758 { 759 // Valid since it is less than maxInvoiceLength. 760 encodedInvoice: "lndcr25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeesrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66zlnttha3xvcs5vdzqchtd462jgkqzxv59jvj5d06ne2wpajp6etptmk5krggr93xywxm6nfapsnln7n5jcgy9s56g2sd2gthvcvd3hqpv6fnqy", 761 }, 762 { 763 // Invalid since it is greater than maxInvoiceLength. 764 encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66fxq08u2ye04k6d2v2hgd8naeu0mfszlz2h6ze5mnufsc7y07s2z5v7vswj7jjcqumqchd646hnrx9pznxfj98565uc84zpc82x3nr8sqqn2zzu", 765 expectedError: ErrInvoiceTooLarge, 766 }, 767 } 768 769 net := chaincfg.MainNetParams() 770 771 for i, test := range tests { 772 _, err := Decode(test.encodedInvoice, net) 773 if err != test.expectedError { 774 t.Errorf("Expected test %d to have error: %v, instead have: %v", 775 i, test.expectedError, err) 776 return 777 } 778 } 779 } 780 781 // TestInvoiceChecksumMalleability ensures that the malleability of the 782 // checksum in bech32 strings cannot cause a signature to become valid and 783 // therefore cause a wrong destination to be decoded for invoices where the 784 // destination is extracted from the signature. 785 func TestInvoiceChecksumMalleability(t *testing.T) { 786 privKeyHex := "7f9f2872307ba178b75434250da5fcac12e9d47fe47d90c1f0cb0641a450cff8" 787 privKeyBytes, _ := hex.DecodeString(privKeyHex) 788 chain := chaincfg.RegNetParams() 789 var payHash [32]byte 790 ts := time.Unix(0, 0) 791 privKey := secp256k1.PrivKeyFromBytes(privKeyBytes) 792 pubKey := privKey.PubKey() 793 msgSigner := MessageSigner{ 794 SignCompact: func(msg []byte) ([]byte, error) { 795 hash := chainhash.HashB(msg) 796 return ecdsa.SignCompact(privKey, hash, true), nil 797 }, 798 } 799 opts := []func(*Invoice){Description("test")} 800 invoice, err := NewInvoice(chain, payHash, ts, opts...) 801 if err != nil { 802 t.Fatal(err) 803 } 804 805 encoded, err := invoice.Encode(msgSigner) 806 if err != nil { 807 t.Fatal(err) 808 } 809 810 t.Logf("encoded %s", encoded) 811 t.Logf("pubkey %x", pubKey.SerializeCompressed()) 812 813 // Changing a bech32 string which checksum ends in "p" to "(q*)p" can 814 // cause the checksum to return as a valid bech32 string _but_ the 815 // signature field immediately preceding it would be mutaded. In rare 816 // cases (about 3%) it is still seen as a valid signature and public 817 // key recovery causes a different node than the originally intended 818 // one to be derived. 819 // 820 // We thus modify the checksum here and verify the invoice gets broken 821 // enough that it fails to decode. 822 if !strings.HasSuffix(encoded, "p") { 823 t.Logf("Invoice: %s", encoded) 824 t.Fatalf("Generated invoice checksum does not end in 'p'") 825 } 826 encoded = encoded[:len(encoded)-1] + "qp" 827 828 _, err = Decode(encoded, chain) 829 if err == nil { 830 t.Fatalf("Did not get expected error when decoding invoice") 831 } 832 833 } 834 835 func compareInvoices(expected, actual *Invoice) error { 836 if !reflect.DeepEqual(expected.Net, actual.Net) { 837 return fmt.Errorf("expected net %v, got %v", 838 expected.Net, actual.Net) 839 } 840 841 if !reflect.DeepEqual(expected.MilliAt, actual.MilliAt) { 842 return fmt.Errorf("expected milli atoms %d, got %d", 843 *expected.MilliAt, *actual.MilliAt) 844 } 845 846 if expected.Timestamp != actual.Timestamp { 847 return fmt.Errorf("expected timestamp %v, got %v", 848 expected.Timestamp, actual.Timestamp) 849 } 850 851 if !compareHashes(expected.PaymentHash, actual.PaymentHash) { 852 return fmt.Errorf("expected payment hash %x, got %x", 853 *expected.PaymentHash, *actual.PaymentHash) 854 } 855 856 if !reflect.DeepEqual(expected.Description, actual.Description) { 857 return fmt.Errorf("expected description \"%s\", got \"%s\"", 858 *expected.Description, *actual.Description) 859 } 860 861 if !comparePubkeys(expected.Destination, actual.Destination) { 862 return fmt.Errorf("expected destination pubkey %x, got %x", 863 expected.Destination.SerializeCompressed(), actual.Destination.SerializeCompressed()) 864 } 865 866 if !compareHashes(expected.DescriptionHash, actual.DescriptionHash) { 867 return fmt.Errorf("expected description hash %x, got %x", 868 *expected.DescriptionHash, *actual.DescriptionHash) 869 } 870 871 if expected.Expiry() != actual.Expiry() { 872 return fmt.Errorf("expected expiry %d, got %d", 873 expected.Expiry(), actual.Expiry()) 874 } 875 876 if !reflect.DeepEqual(expected.FallbackAddr, actual.FallbackAddr) { 877 return fmt.Errorf("expected FallbackAddr %v, got %v", 878 expected.FallbackAddr, actual.FallbackAddr) 879 } 880 881 if len(expected.RouteHints) != len(actual.RouteHints) { 882 return fmt.Errorf("expected %d RouteHints, got %d", 883 len(expected.RouteHints), len(actual.RouteHints)) 884 } 885 886 for i, routeHint := range expected.RouteHints { 887 err := compareRouteHints(routeHint, actual.RouteHints[i]) 888 if err != nil { 889 return err 890 } 891 } 892 893 if !reflect.DeepEqual(expected.Features, actual.Features) { 894 return fmt.Errorf("expected features %v, got %v", 895 expected.Features, actual.Features) 896 } 897 898 return nil 899 } 900 901 func comparePubkeys(a, b *secp256k1.PublicKey) bool { 902 if a == b { 903 return true 904 } 905 if a == nil && b != nil { 906 return false 907 } 908 if b == nil && a != nil { 909 return false 910 } 911 return a.IsEqual(b) 912 } 913 914 func compareHashes(a, b *[32]byte) bool { 915 if a == b { 916 return true 917 } 918 if a == nil && b != nil { 919 return false 920 } 921 if b == nil && a != nil { 922 return false 923 } 924 return bytes.Equal(a[:], b[:]) 925 } 926 927 func compareRouteHints(a, b []HopHint) error { 928 if len(a) != len(b) { 929 return fmt.Errorf("expected len routingInfo %d, got %d", 930 len(a), len(b)) 931 } 932 933 for i := 0; i < len(a); i++ { 934 if !comparePubkeys(a[i].NodeID, b[i].NodeID) { 935 return fmt.Errorf("expected routeHint nodeID %x, "+ 936 "got %x", a[i].NodeID.SerializeCompressed(), b[i].NodeID.SerializeCompressed()) 937 } 938 939 if a[i].ChannelID != b[i].ChannelID { 940 return fmt.Errorf("expected routeHint channelID "+ 941 "%d, got %d", a[i].ChannelID, b[i].ChannelID) 942 } 943 944 if a[i].FeeBaseMAtoms != b[i].FeeBaseMAtoms { 945 return fmt.Errorf("expected routeHint feeBaseMAtoms %d, got %d", 946 a[i].FeeBaseMAtoms, b[i].FeeBaseMAtoms) 947 } 948 949 if a[i].FeeProportionalMillionths != b[i].FeeProportionalMillionths { 950 return fmt.Errorf("expected routeHint feeProportionalMillionths %d, got %d", 951 a[i].FeeProportionalMillionths, b[i].FeeProportionalMillionths) 952 } 953 954 if a[i].CLTVExpiryDelta != b[i].CLTVExpiryDelta { 955 return fmt.Errorf("expected routeHint cltvExpiryDelta "+ 956 "%d, got %d", a[i].CLTVExpiryDelta, b[i].CLTVExpiryDelta) 957 } 958 } 959 960 return nil 961 }