From 19de556df26f7a52a08cada0ff4fb75e31a4ea5c Mon Sep 17 00:00:00 2001 From: anon <109770337+lnproxy@users.noreply.github.com> Date: Thu, 20 Oct 2022 16:16:39 -0400 Subject: [PATCH 1/3] functions required for lnproxy support Tried using ln-decodepay to extract the payment hash but it messed up the go.mod so I wrote a bad version myself. --- lnproxy.go | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 lnproxy.go diff --git a/lnproxy.go b/lnproxy.go new file mode 100644 index 0000000..fa4f5ee --- /dev/null +++ b/lnproxy.go @@ -0,0 +1,87 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "net/http" + "regexp" + "strconv" + "strings" +) + +var lnproxyClient = &http.Client{} + +func wrapInvoice(bolt11 string, msat, routing_msat int) (string, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s/%s?routing_msat=%d", s.LnproxyURL, bolt11, routing_msat), nil) + if err != nil { + return "", err + } + resp, err := lnproxyClient.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + buf := new(strings.Builder) + _, err = io.Copy(buf, resp.Body) + if err != nil { + return "", err + } + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("lnproxy error: %s", buf.String()) + } + wbolt11 := strings.TrimSpace(buf.String()) + a, h, err := extractInvoiceDetails([]byte(bolt11)) + if err != nil { + return "", err + } + wa, wh, err := extractInvoiceDetails([]byte(wbolt11)) + if err != nil { + return "", err + } + if bytes.Compare(h, wh) != 0 { + return "", fmt.Errorf("Wrapped payment hash does not match!") + } + if (a + routing_msat) != wa { + return "", fmt.Errorf("Wrapped routing budget too high!") + } + return wbolt11, nil +} + +var CharSet = []byte("qpzry9x8gf2tvdw0s3jn54khce6mua7l") + +var validInvoice = regexp.MustCompile("^lnbc(?:[0-9]+[pnum])?1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]+$") + +func extractInvoiceDetails(invoice []byte) (int, []byte, error) { + invoice = bytes.ToLower(invoice) + pos := bytes.LastIndexByte(invoice, byte('1')) + if pos == -1 || !validInvoice.Match(invoice) { + return 0, nil, fmt.Errorf("Invalid invoice") + } + + var msat int + var err error + if pos > 4 { + msat, err = strconv.Atoi(string(invoice[4 : pos-1])) + if err != nil { + return 0, nil, err + } + switch invoice[pos-1] { + case byte('p'): + msat = msat / 10 + case byte('n'): + msat = msat * 100 + case byte('u'): + msat = msat * 100_000 + case byte('m'): + msat = msat * 100_000_000 + } + } + for i := pos + 8; i < len(invoice); { + if bytes.Compare(invoice[i:i+3], []byte("pp5")) == 0 { + return msat, invoice[i+1+2 : i+1+2+52], nil + } + i += 3 + bytes.Index(CharSet, invoice[i+1:i+2])*32 + bytes.Index(CharSet, invoice[i+2:i+3]) + } + return 0, nil, fmt.Errorf("No 'p' tag") +} From a58423ce45467bb2180d632a0d3af666db5e13eb Mon Sep 17 00:00:00 2001 From: anon <109770337+lnproxy@users.noreply.github.com> Date: Thu, 20 Oct 2022 16:18:04 -0400 Subject: [PATCH 2/3] config parameters for lnproxy --- main.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/main.go b/main.go index 77c0e5e..e7f36c3 100644 --- a/main.go +++ b/main.go @@ -30,6 +30,11 @@ type Settings struct { ForceMigrate bool `envconfig:"FORCE_MIGRATE" required:"false" default:false` TorProxyURL string `envconfig:"TOR_PROXY_URL"` + + Lnproxy bool `envconfig:"LNPROXY" default:"false"` + LnproxyURL string `envconfig:"LNPROXY_URL" default:"https://lnproxy.org/api"` + LnproxyRoutingBaseMsat int `envconfig:"LNPROXY_ROUTING_BASE_MSAT" default:"1000"` + LnproxyRoutingPpmMsat int `envconfig:"LNPROXY_ROUTING_PPM" default:"6000"` } var s Settings From b137e557035b2a3ec05d2b9d4692b164e5b6ffed Mon Sep 17 00:00:00 2001 From: anon <109770337+lnproxy@users.noreply.github.com> Date: Thu, 20 Oct 2022 16:19:05 -0400 Subject: [PATCH 3/3] wrap invoice with lnproxy before returning it --- lnurl.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lnurl.go b/lnurl.go index 559021f..60cafe8 100644 --- a/lnurl.go +++ b/lnurl.go @@ -80,6 +80,11 @@ func handleLNURL(w http.ResponseWriter, r *http.Request) { return } + routing_msat := s.LnproxyRoutingBaseMsat + (s.LnproxyRoutingPpmMsat*msat)/1_000_000 + if s.Lnproxy { + msat = msat - routing_msat + } + bolt11, err := makeInvoice(params, msat, nil) if err != nil { json.NewEncoder(w).Encode( @@ -87,6 +92,15 @@ func handleLNURL(w http.ResponseWriter, r *http.Request) { return } + if s.Lnproxy { + bolt11, err = wrapInvoice(bolt11, msat, routing_msat) + if err != nil { + json.NewEncoder(w).Encode( + lnurl.ErrorResponse("failed to wrap invoice: " + err.Error())) + return + } + } + json.NewEncoder(w).Encode(lnurl.LNURLPayResponse2{ LNURLResponse: lnurl.LNURLResponse{Status: "OK"}, PR: bolt11,