Skip to content

Commit

Permalink
Change required encoding of preimage from Base64 to hex
Browse files Browse the repository at this point in the history
- Hex is the typical used encoding for preimages and their hashes
  • Loading branch information
philippgille committed Sep 10, 2018
1 parent a290870 commit b1d5c04
Show file tree
Hide file tree
Showing 10 changed files with 56 additions and 52 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------------
Expand Down
4 changes: 4 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
-------------------

Expand Down
2 changes: 1 addition & 1 deletion examples/qr-code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
19 changes: 10 additions & 9 deletions ln/ln.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
10 changes: 5 additions & 5 deletions ln/ln_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
24 changes: 10 additions & 14 deletions ln/lnd.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package ln

import (
"context"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"io/ioutil"
Expand Down Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions wall/echo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand All @@ -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)
}
Expand Down
10 changes: 5 additions & 5 deletions wall/gin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}
Expand Down
17 changes: 10 additions & 7 deletions wall/middleware.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package wall

import (
"encoding/hex"
"log"
"os"
"reflect"
Expand Down Expand Up @@ -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
}
Expand All @@ -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 {
Expand All @@ -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
}
Expand Down
10 changes: 5 additions & 5 deletions wall/stdlib.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}
Expand Down

0 comments on commit b1d5c04

Please sign in to comment.