Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ case class Bolt11Invoice(prefix: String, amount_opt: Option[MilliSatoshi], creat
require(tags.collect { case _: Bolt11Invoice.PaymentHash => }.size == 1, "there must be exactly one payment hash tag")
require(tags.collect { case Bolt11Invoice.Description(_) | Bolt11Invoice.DescriptionHash(_) => }.size == 1, "there must be exactly one description tag or one description hash tag")
require(tags.collect { case _: Bolt11Invoice.PaymentSecret => }.size == 1, "there must be exactly one payment secret tag")
require(tags.collect { case f: Bolt11Invoice.FallbackAddress => Try(FallbackAddress(Bolt11Invoice.FallbackAddress.toAddress(f, prefix))) }.forall(_.isSuccess), "invalid fallback address")
require(Features.validateFeatureGraph(features).isEmpty, Features.validateFeatureGraph(features).map(_.message))

lazy val paymentHash: ByteVector32 = tags.collectFirst { case p: Bolt11Invoice.PaymentHash => p.hash }.get
Expand Down Expand Up @@ -269,16 +270,15 @@ object Bolt11Invoice {
}

def toAddress(f: FallbackAddress, prefix: String): String = {
import f.data
f.version match {
case 17 if prefix == "lnbc" => Base58Check.encode(Base58.Prefix.PubkeyAddress, data.toArray)
case 18 if prefix == "lnbc" => Base58Check.encode(Base58.Prefix.ScriptAddress, data.toArray)
case 17 if prefix == "lntb" || prefix == "lnbcrt" || prefix == "lntbs" => Base58Check.encode(Base58.Prefix.PubkeyAddressTestnet, data.toArray)
case 18 if prefix == "lntb" || prefix == "lnbcrt" || prefix == "lntbs" => Base58Check.encode(Base58.Prefix.ScriptAddressTestnet, data.toArray)
case version if prefix == "lnbc" => Bech32.encodeWitnessAddress("bc", version, data.toArray)
case version if prefix == "lntb" => Bech32.encodeWitnessAddress("tb", version, data.toArray)
case version if prefix == "lnbcrt" => Bech32.encodeWitnessAddress("bcrt", version, data.toArray)
case version if prefix == "lntbs" => Bech32.encodeWitnessAddress("tb", version, data.toArray)
case 17 if prefix == "lnbc" => Base58Check.encode(Base58.Prefix.PubkeyAddress, f.data.toArray)
case 18 if prefix == "lnbc" => Base58Check.encode(Base58.Prefix.ScriptAddress, f.data.toArray)
case 17 if prefix == "lntb" || prefix == "lnbcrt" || prefix == "lntbs" => Base58Check.encode(Base58.Prefix.PubkeyAddressTestnet, f.data.toArray)
case 18 if prefix == "lntb" || prefix == "lnbcrt" || prefix == "lntbs" => Base58Check.encode(Base58.Prefix.ScriptAddressTestnet, f.data.toArray)
case version if prefix == "lnbc" => Bech32.encodeWitnessAddress("bc", version, f.data.toArray)
case version if prefix == "lntb" => Bech32.encodeWitnessAddress("tb", version, f.data.toArray)
case version if prefix == "lnbcrt" => Bech32.encodeWitnessAddress("bcrt", version, f.data.toArray)
case version if prefix == "lntbs" => Bech32.encodeWitnessAddress("tb", version, f.data.toArray)
}
}
}
Expand Down Expand Up @@ -516,11 +516,11 @@ object Bolt11Invoice {
val lowercaseInput = input.toLowerCase
val separatorIndex = lowercaseInput.lastIndexOf('1')
val hrp = lowercaseInput.take(separatorIndex)
val prefix: String = prefixes.values.toSeq.sortBy(_.length).findLast(prefix => hrp.startsWith(prefix)).getOrElse(throw new RuntimeException("unknown prefix"))
val prefix = prefixes.values.toSeq.sortBy(_.length).findLast(prefix => hrp.startsWith(prefix)).getOrElse(throw new RuntimeException("unknown prefix"))
val data = string2Bits(lowercaseInput.slice(separatorIndex + 1, lowercaseInput.length - 6)) // 6 == checksum size
val bolt11Data = Codecs.bolt11DataCodec.decode(data).require.value
val signature = ByteVector64(bolt11Data.signature.take(64))
val message: ByteVector = ByteVector.view(hrp.getBytes) ++ data.dropRight(520).toByteVector // we drop the sig bytes
val message = ByteVector.view(hrp.getBytes) ++ data.dropRight(520).toByteVector // we drop the sig bytes
val recid = bolt11Data.signature.last
val pub = Crypto.recoverPublicKey(signature, Crypto.sha256(message), recid)
// README: since we use pubkey recovery to compute the node id from the message and signature, we don't check the signature.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,33 +306,6 @@ class Bolt11InvoiceSpec extends AnyFunSuite {
assert(invoice.sign(priv).toString == ref)
}

test("On mainnet, please send $30 for coffee beans to the same peer, which supports features 8, 14 and 99, using secret 0x1111111111111111111111111111111111111111111111111111111111111111") {
val refs = Seq(
"lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q5sqqqqqqqqqqqqqqqqsgq2a25dxl5hrntdtn6zvydt7d66hyzsyhqs4wdynavys42xgl6sgx9c4g7me86a27t07mdtfry458rtjr0v92cnmswpsjscgt2vcse3sgpz3uapa",
// All upper-case
"lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q5sqqqqqqqqqqqqqqqqsgq2a25dxl5hrntdtn6zvydt7d66hyzsyhqs4wdynavys42xgl6sgx9c4g7me86a27t07mdtfry458rtjr0v92cnmswpsjscgt2vcse3sgpz3uapa".toUpperCase,
// With ignored fields
"lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q5sqqqqqqqqqqqqqqqqsgq2qrqqqfppnqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqppnqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpp4qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhpnqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhp4qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqspnqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsp4qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnp5qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnpkqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqz599y53s3ujmcfjp5xrdap68qxymkqphwsexhmhr8wdz5usdzkzrse33chw6dlp3jhuhge9ley7j2ayx36kawe7kmgg8sv5ugdyusdcqzn8z9x"
)

for (ref <- refs) {
val Success(invoice) = Bolt11Invoice.fromString(ref)
assert(invoice.prefix == "lnbc")
assert(invoice.amount_opt.contains(2500000000L msat))
assert(invoice.paymentHash.bytes == hex"0001020304050607080900010203040506070809000102030405060708090102")
assert(invoice.paymentSecret.bytes == hex"1111111111111111111111111111111111111111111111111111111111111111")
assert(invoice.createdAt == TimestampSecond(1496314658L))
assert(invoice.nodeId == PublicKey(hex"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad"))
assert(invoice.description == Left("coffee beans"))
assert(features2bits(invoice.features) == bin"1000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000100000000")
assert(!invoice.features.hasFeature(BasicMultiPartPayment))
assert(invoice.features.hasFeature(PaymentSecret, Some(Mandatory)))
assert(!invoice.features.hasFeature(TrampolinePaymentPrototype))
assert(TestConstants.Alice.nodeParams.features.invoiceFeatures().areSupported(invoice.features))
assert(invoice.sign(priv).toString == ref.toLowerCase)
}
}

test("On mainnet, please send $30 for coffee beans to the same peer, which supports features 8, 14, 99 and 100, using secret 0x1111111111111111111111111111111111111111111111111111111111111111") {
val ref = "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q4psqqqqqqqqqqqqqqqqsgqtqyx5vggfcsll4wu246hz02kp85x4katwsk9639we5n5yngc3yhqkm35jnjw4len8vrnqnf5ejh0mzj9n3vz2px97evektfm2l6wqccp3y7372"
val Success(invoice) = Bolt11Invoice.fromString(ref)
Expand Down Expand Up @@ -430,6 +403,8 @@ class Bolt11InvoiceSpec extends AnyFunSuite {
"lnbc1p5q54jjpp5fe0dhqdt4m97psq0fv3wjlk95cclnatvuvq49xtnc8rzrp0dysusdqqcqzzsxqrrs0fppqy6uew5229e67r9xzzm9mjyfwseclstdgsp5rnanj9x5rnanj9xnq28hhgd6c7yxlmh6lta047h6lqqqqqqqqqqqrqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6qqqqqqqqqqqqqqqqqqq9kvnknh7ug5mttnqqqqqqqqq8849gwfhvnp9rqpe0cy97",
// Invalid min_final_expiry_delta_blocks.
"lnbc1p5q54jjpp5fe0dhqdt4m97psq0fv3wjlk95cclnatvuvq49xtnc8rzrp0d5susdqqcqg3vrywwwjsppqy6uew5229e67rzxzzm9mjyfwseclstdgsp5rnanj9xnq28hhgd6c7yxlmh6lta047h6lqqqqqqqqqqqrqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq9kvnknh7ug5mttn2yu5ha6m98cpda2rtwu08849gwfhvnp9rqpqqqqqqqqqg58lts",
// Invalid fallback address.
"lnbc1qzupp9qsp5pvgsuqqpgczuppczc3pcz3syzy8q2xqqqqqqqqqqqqqqqqqygh9qpp5s7zxqqqqqqqqqqyqymqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhp5qs97qqqqqqqpqqyqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqptfqptfqptfqptfqptfqptfqptfqptfq95xtfqptfqp3w9chzut3w9chj95xw7tfpp35qqqw9chzuqt3w9chzut3qptfqptfqptfqptfqptfqptfqpqw9cqqqqt28y39",
)
for (ref <- refs) {
assert(Bolt11Invoice.fromString(ref).isFailure)
Expand Down