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 @@ -314,8 +314,21 @@ object OfferTypes {
Right(Offer(records))
}

/**
* An offer string can be split with '+' to fit in places with a low character limit. This validates that the string adheres to the spec format to guard against copy-pasting errors.
* @return a lowercase string with '+' and whitespaces removed
*/
private def validateFormat(s: String): String = {
val lowercase = s.toLowerCase
require(s == lowercase || s == s.toUpperCase)
require(lowercase.head == 'l')
require(Bech32.alphabet.contains(lowercase.last))
require(!lowercase.matches(".*\\+\\s*\\+.*"))
lowercase.replaceAll("\\+\\s*", "")
}

def decode(s: String): Try[Offer] = Try {
val triple = Bech32.decodeBytes(s.toLowerCase, true)
val triple = Bech32.decodeBytes(validateFormat(s), true)
val prefix = triple.getFirst
val encoded = triple.getSecond
val encoding = triple.getThird
Expand Down
62 changes: 62 additions & 0 deletions eclair-core/src/test/resources/format-string-test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
[
{
"comment": "A complete string is valid",
"valid": true,
"string": "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg"
},
{
"comment": "Uppercase is valid",
"valid": true,
"string": "LNO1PQPS7SJQPGTYZM3QV4UXZMTSD3JJQER9WD3HY6TSW35K7MSJZFPY7NZ5YQCNYGRFDEJ82UM5WF5K2UCKYYPWA3EYT44H6TXTXQUQH7LZ5DJGE4AFGFJN7K4RGRKUAG0JSD5XVXG"
},
{
"comment": "+ can join anywhere",
"valid": true,
"string": "l+no1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg"
},
{
"comment": "Multiple + can join",
"valid": true,
"string": "lno1pqps7sjqpgt+yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+5k7msjzfpy7nz5yqcn+ygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+5xvxg"
},
{
"comment": "+ can be followed by whitespace",
"valid": true,
"string": "lno1pqps7sjqpgt+ yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+ 5k7msjzfpy7nz5yqcn+\nygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+\r\n 5xvxg"
},
{
"comment": "+ can be followed by whitespace, UPPERCASE",
"valid": true,
"string": "LNO1PQPS7SJQPGT+ YZM3QV4UXZMTSD3JJQER9WD3HY6TSW3+ 5K7MSJZFPY7NZ5YQCN+\nYGRFDEJ82UM5WF5K2UCKYYPWA3EYT44H6TXTXQUQH7LZ5DJGE4AFGFJN7K4RGRKUAG0JSD+\r\n 5XVXG"
},
{
"comment": "Mixed case is invalid",
"valid": false,
"string": "LnO1PqPs7sJqPgTyZm3qV4UxZmTsD3JjQeR9Wd3hY6TsW35k7mSjZfPy7nZ5YqCnYgRfDeJ82uM5Wf5k2uCkYyPwA3EyT44h6tXtXqUqH7Lz5dJgE4AfGfJn7k4rGrKuAg0jSd5xVxG"
},
{
"comment": "+ must be surrounded by bech32 characters",
"valid": false,
"string": "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+"
},
{
"comment": "+ must be surrounded by bech32 characters",
"valid": false,
"string": "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+ "
},
{
"comment": "+ must be surrounded by bech32 characters",
"valid": false,
"string": "+lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg"
},
{
"comment": "+ must be surrounded by bech32 characters",
"valid": false,
"string": "+ lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg"
},
{
"comment": "+ must be surrounded by bech32 characters",
"valid": false,
"string": "ln++o1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg"
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -319,4 +319,17 @@ class OfferTypesSpec extends AnyFunSuite {
}
}
}

case class FormatTestVector(comment: String, valid: Boolean, string: String)

test("string format spec test vectors") {
implicit val formats: DefaultFormats.type = DefaultFormats

val src = Source.fromFile(new File(getClass.getResource(s"/format-string-test.json").getFile))
val testVectors = JsonMethods.parse(src.mkString).extract[Seq[FormatTestVector]]
src.close()
for (vector <- testVectors) {
assert(Offer.decode(vector.string).isSuccess == vector.valid, vector.comment)
}
}
}
Loading