From b1d5c04ba1ea6394ccbc4b134dbeedd0a188abe4 Mon Sep 17 00:00:00 2001 From: Philipp Gille Date: Mon, 10 Sep 2018 01:53:03 +0200 Subject: [PATCH] Change required encoding of preimage from Base64 to hex - Hex is the typical used encoding for preimages and their hashes --- README.md | 2 +- RELEASES.md | 4 ++++ examples/qr-code/README.md | 2 +- ln/ln.go | 19 ++++++++++--------- ln/ln_test.go | 10 +++++----- ln/lnd.go | 24 ++++++++++-------------- wall/echo.go | 10 +++++----- wall/gin.go | 10 +++++----- wall/middleware.go | 17 ++++++++++------- wall/stdlib.go | 10 +++++----- 10 files changed, 56 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 31eae95..cf11654 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ How it works With `ln-paywall` you can simply use one of the provided middlewares in your Go web service to have your web service do two things: 1. The first request gets rejected with the `402 Payment Required` HTTP status, a `Content-Type: application/vnd.lightning.bolt11` header and a Lightning ([BOLT-11](https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md)-conforming) invoice in the body -2. The second request must contain a `X-Preimage` header with the preimage of the paid Lightning invoice. The middleware checks if 1) the invoice was paid and 2) not already used for a previous request. If both preconditions are met, it continues to the next middleware or final request handler. +2. The second request must contain a `X-Preimage` header with the preimage of the paid Lightning invoice (hex encoded). The middleware checks if 1) the invoice was paid and 2) not already used for a previous request. If both preconditions are met, it continues to the next middleware or final request handler. Prerequisites ------------- diff --git a/RELEASES.md b/RELEASES.md index 29d786d..464ecce 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -8,6 +8,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a vNext ----- +### Breaking changes + +- Changed: The preimage in the `X-Preimage` header must now be hex encoded instead of Base64 encoded. The hex encoded representation is the typical representation, as used by "lncli listpayments", Eclair on Android and bolt11 payment request decoders like [https://lndecode.com](https://lndecode.com). Base64 was used previously because "lncli listinvoices" uses that encoding. + v0.4.0 (2018-09-03) ------------------- diff --git a/examples/qr-code/README.md b/examples/qr-code/README.md index c2d3e66..c369984 100644 --- a/examples/qr-code/README.md +++ b/examples/qr-code/README.md @@ -13,7 +13,7 @@ Usage 3. Run the container: `docker run -d --name qr-code -v $(pwd)/data/:/root/data/ -p 8080:8080 philippgille/qr-code -addr "123.123.123.123:10009"` 4. Send a request: `curl http://localhost:8080/qr?data=testtext` 5. Pay the invoice from the response via the Lightning Network -6. Send the request again, this time with the preimage as payment proof: `curl -H "x-preimage: c29tZSBwcmVpbWFnZQ==" http://localhost:8080/qr?data=testtext` +6. Send the request again, this time with the preimage as payment proof (hex encoded) and the data as query parameters: `curl -H "x-preimage: 119969c2338798cd56708126b5d6c0f6f5e75ed38da7a409b0081d94b4dacbf8" http://localhost:8080/qr?data=testtext` The response contains the QR code as PNG image. diff --git a/ln/ln.go b/ln/ln.go index ef03439..8d6d702 100644 --- a/ln/ln.go +++ b/ln/ln.go @@ -2,18 +2,19 @@ package ln import ( "crypto/sha256" - "encoding/base64" + "encoding/hex" ) -// HashPreimage hashes the Base64 preimage and encodes the hash in Base64. -// It's the same format that's being shown by lncli listinvoices (preimage as well as hash). -func HashPreimage(preimage string) (string, error) { - decodedPreimage, err := base64.StdEncoding.DecodeString(preimage) +// HashPreimage turns a hex encoded preimage into a hex encoded preimage hash. +// It's the same format that's being used by "lncli listpayments", Eclair on Android and bolt11 payment request decoders like https://lndecode.com. +// Only "lncli listinvoices" uses Base64. +func HashPreimage(preimageHex string) (string, error) { + // Decode from hex, hash, encode to hex + preimage, err := hex.DecodeString(preimageHex) if err != nil { return "", err } - hash := sha256.Sum256([]byte(decodedPreimage)) - hashSlice := hash[:] - encodedHash := base64.StdEncoding.EncodeToString(hashSlice) - return encodedHash, nil + hashByteArray := sha256.Sum256(preimage) + preimageHashHex := hex.EncodeToString(hashByteArray[:]) + return preimageHashHex, nil } diff --git a/ln/ln_test.go b/ln/ln_test.go index 19fd162..ee37abc 100644 --- a/ln/ln_test.go +++ b/ln/ln_test.go @@ -8,11 +8,11 @@ import ( // TestHashPreimage tests if the result of the HashPreimage function is correct. func TestHashPreimage(t *testing.T) { - // Correct preimage form, taken from an invoice JSON in lnd - preimage := "CtkknKfJ237o8jbippg5NziFZgkUvk9cN20NCuPvqxQ=" - // Taken from the same invoice JSON in lnd - expected := "iYYYRTzq0XZgBo1aIAv4KZTzq1PCTmf6nnWvyHMB4C0=" - actual, err := ln.HashPreimage(preimage) + // Correct preimage form, taken from a payment JSON in lnd + preimageHex := "119969c2338798cd56708126b5d6c0f6f5e75ed38da7a409b0081d94b4dacbf8" + // Taken from the same payment JSON in lnd + expected := "bf3e0e73d4bb1ee9d68ca8d1078213d059e23d6e1c8a14b3df93faf87aa4fed3" + actual, err := ln.HashPreimage(preimageHex) if err != nil { t.Errorf("An error occurred during the test: %v\n", err) } diff --git a/ln/lnd.go b/ln/lnd.go index 1ee5ec4..7320fdd 100644 --- a/ln/lnd.go +++ b/ln/lnd.go @@ -2,8 +2,6 @@ package ln import ( "context" - "crypto/sha256" - "encoding/base64" "encoding/hex" "fmt" "io/ioutil" @@ -39,27 +37,25 @@ func (c LNDclient) GenerateInvoice(amount int64, memo string) (string, error) { return res.GetPaymentRequest(), nil } -// CheckInvoice takes a Base64 encoded preimage, fetches the corresponding invoice, -// and checks if the invoice was settled. -// An error is returned if the preimage contains invalid Base64 characters or if no corresponding invoice was found. +// CheckInvoice takes a hex encoded preimage and checks if the corresponding invoice was settled. +// An error is returned if the preimage isn't properly encoded or if no corresponding invoice was found. // False is returned if the invoice isn't settled. -func (c LNDclient) CheckInvoice(preimage string) (bool, error) { - // Hash the preimage so we can get the corresponding invoice to check if it's settled - decodedPreimage, err := base64.StdEncoding.DecodeString(preimage) +func (c LNDclient) CheckInvoice(preimageHex string) (bool, error) { + preimageHashHex, err := HashPreimage(preimageHex) if err != nil { return false, err } - hash := sha256.Sum256([]byte(decodedPreimage)) - hashSlice := hash[:] + // Ignore the error because the reverse (encoding) was just done previously, so this must work + plainHash, _ := hex.DecodeString(preimageHashHex) + + log.Printf("Checking invoice for hash %v\n", preimageHashHex) // Get the invoice for that hash paymentHash := lnrpc.PaymentHash{ - RHash: hashSlice, + RHash: plainHash, // Hex encoded, must be exactly 32 byte - RHashStr: hex.EncodeToString(hashSlice), + RHashStr: preimageHashHex, } - encodedHash := base64.StdEncoding.EncodeToString(hashSlice) - log.Printf("Checking invoice for hash %v\n", encodedHash) invoice, err := c.lndClient.LookupInvoice(c.ctx, &paymentHash) if err != nil { return false, err diff --git a/wall/echo.go b/wall/echo.go index b35fde6..7302734 100644 --- a/wall/echo.go +++ b/wall/echo.go @@ -23,8 +23,8 @@ func NewEchoMiddleware(invoiceOptions InvoiceOptions, lnClient LNclient, storage return next(ctx) } // Check if the request contains a header with the preimage that we need to check if the requester paid - preimage := ctx.Request().Header.Get("x-preimage") - if preimage == "" { + preimageHex := ctx.Request().Header.Get("x-preimage") + if preimageHex == "" { // Generate the invoice invoice, err := lnClient.GenerateInvoice(invoiceOptions.Price, invoiceOptions.Memo) if err != nil { @@ -47,7 +47,7 @@ func NewEchoMiddleware(invoiceOptions InvoiceOptions, lnClient LNclient, storage } } // Check if the provided preimage belongs to a settled API payment invoice and that it wasn't already used. Also store used preimages. - invalidPreimageMsg, err := handlePreimage(preimage, storageClient, lnClient) + invalidPreimageMsg, err := handlePreimage(preimageHex, storageClient, lnClient) if err != nil { errorMsg := fmt.Sprintf("An error occurred during checking the preimage: %+v", err) log.Printf("%v\n", errorMsg) @@ -57,14 +57,14 @@ func NewEchoMiddleware(invoiceOptions InvoiceOptions, lnClient LNclient, storage Internal: err, } } else if invalidPreimageMsg != "" { - log.Printf("%v: %v\n", invalidPreimageMsg, preimage) + log.Printf("%v: %v\n", invalidPreimageMsg, preimageHex) return &echo.HTTPError{ Code: http.StatusBadRequest, Message: invalidPreimageMsg, Internal: err, } } else { - preimageHash, err := ln.HashPreimage(preimage) + preimageHash, err := ln.HashPreimage(preimageHex) if err == nil { stdOutLogger.Printf("The provided preimage is valid. Continuing to the next HandlerFunc. Preimage hash: %v\n", preimageHash) } diff --git a/wall/gin.go b/wall/gin.go index fab0bfe..c8efef3 100644 --- a/wall/gin.go +++ b/wall/gin.go @@ -14,8 +14,8 @@ func NewGinMiddleware(invoiceOptions InvoiceOptions, lnClient LNclient, storageC invoiceOptions = assignDefaultValues(invoiceOptions) return func(ctx *gin.Context) { // Check if the request contains a header with the preimage that we need to check if the requester paid - preimage := ctx.GetHeader("x-preimage") - if preimage == "" { + preimageHex := ctx.GetHeader("x-preimage") + if preimageHex == "" { // Generate the invoice invoice, err := lnClient.GenerateInvoice(invoiceOptions.Price, invoiceOptions.Memo) if err != nil { @@ -33,18 +33,18 @@ func NewGinMiddleware(invoiceOptions InvoiceOptions, lnClient LNclient, storageC } } else { // Check if the provided preimage belongs to a settled API payment invoice and that it wasn't already used. Also store used preimages. - invalidPreimageMsg, err := handlePreimage(preimage, storageClient, lnClient) + invalidPreimageMsg, err := handlePreimage(preimageHex, storageClient, lnClient) if err != nil { errorMsg := fmt.Sprintf("An error occurred during checking the preimage: %+v", err) log.Printf("%v\n", errorMsg) http.Error(ctx.Writer, errorMsg, http.StatusInternalServerError) ctx.Abort() } else if invalidPreimageMsg != "" { - log.Printf("%v: %v\n", invalidPreimageMsg, preimage) + log.Printf("%v: %v\n", invalidPreimageMsg, preimageHex) http.Error(ctx.Writer, invalidPreimageMsg, http.StatusBadRequest) ctx.Abort() } else { - preimageHash, err := ln.HashPreimage(preimage) + preimageHash, err := ln.HashPreimage(preimageHex) if err == nil { stdOutLogger.Printf("The provided preimage is valid. Continuing to the next handler. Preimage hash: %v\n", preimageHash) } diff --git a/wall/middleware.go b/wall/middleware.go index 5c48461..5b6210e 100644 --- a/wall/middleware.go +++ b/wall/middleware.go @@ -1,6 +1,7 @@ package wall import ( + "encoding/hex" "log" "os" "reflect" @@ -52,9 +53,9 @@ type LNclient interface { // The string contains detailed info about the result in case the preimage is invalid. // The error is only non-nil if an error occurs during the check (like the LN node can't be reached). // The preimage is only valid if the string is empty and the error is nil. -func handlePreimage(preimage string, storageClient StorageClient, lnClient LNclient) (string, error) { +func handlePreimage(preimageHex string, storageClient StorageClient, lnClient LNclient) (string, error) { // Check if it was already used before - wasUsed, err := storageClient.WasUsed(preimage) + wasUsed, err := storageClient.WasUsed(preimageHex) if err != nil { return "", err } @@ -64,12 +65,14 @@ func handlePreimage(preimage string, storageClient StorageClient, lnClient LNcli } // Check if a corresponding invoice exists and is settled - settled, err := lnClient.CheckInvoice(preimage) + settled, err := lnClient.CheckInvoice(preimageHex) if err != nil { // Returning a non-nil error leads to an "internal server error", but in some cases it's a "bad request". - // TODO: Both checks should be done in a more robust and elegant way - if reflect.TypeOf(err).Name() == "CorruptInputError" { - return "The provided preimage contains invalid Base64 characters", nil + // Handle those cases here. + // TODO: Checks should be done in a more robust and elegant way + if reflect.TypeOf(err).Name() == "InvalidByteError" || + err == hex.ErrLength { + return "The provided preimage isn't properly hex encoded", nil } else if strings.Contains(err.Error(), "unable to locate invoice") { return "No corresponding invoice was found for the provided preimage", nil } else { @@ -82,7 +85,7 @@ func handlePreimage(preimage string, storageClient StorageClient, lnClient LNcli // Key not found, so it wasn't used before. // Insert key for future checks. - err = storageClient.SetUsed(preimage) + err = storageClient.SetUsed(preimageHex) if err != nil { return "", err } diff --git a/wall/stdlib.go b/wall/stdlib.go index b76006e..fa6a8e5 100644 --- a/wall/stdlib.go +++ b/wall/stdlib.go @@ -26,8 +26,8 @@ func createHandlerFunc(invoiceOptions InvoiceOptions, lnClient LNclient, storage invoiceOptions = assignDefaultValues(invoiceOptions) return func(w http.ResponseWriter, r *http.Request) { // Check if the request contains a header with the preimage that we need to check if the requester paid - preimage := r.Header.Get("x-preimage") - if preimage == "" { + preimageHex := r.Header.Get("x-preimage") + if preimageHex == "" { // Generate the invoice invoice, err := lnClient.GenerateInvoice(invoiceOptions.Price, invoiceOptions.Memo) if err != nil { @@ -44,16 +44,16 @@ func createHandlerFunc(invoiceOptions InvoiceOptions, lnClient LNclient, storage } } else { // Check if the provided preimage belongs to a settled API payment invoice and that it wasn't already used. Also store used preimages. - invalidPreimageMsg, err := handlePreimage(preimage, storageClient, lnClient) + invalidPreimageMsg, err := handlePreimage(preimageHex, storageClient, lnClient) if err != nil { errorMsg := fmt.Sprintf("An error occurred during checking the preimage: %+v", err) log.Printf("%v\n", errorMsg) http.Error(w, errorMsg, http.StatusInternalServerError) } else if invalidPreimageMsg != "" { - log.Printf("%v: %v\n", invalidPreimageMsg, preimage) + log.Printf("%v: %v\n", invalidPreimageMsg, preimageHex) http.Error(w, invalidPreimageMsg, http.StatusBadRequest) } else { - preimageHash, err := ln.HashPreimage(preimage) + preimageHash, err := ln.HashPreimage(preimageHex) if err == nil { stdOutLogger.Printf("The provided preimage is valid. Continuing to the next %v. Preimage hash: %v\n", handlingType, preimageHash) }