mirror of
https://github.com/muun/recovery.git
synced 2025-07-20 22:11:16 -04:00
277 lines
7.6 KiB
Go
277 lines
7.6 KiB
Go
package libwallet
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/lightningnetwork/lnd/zpay32"
|
|
)
|
|
|
|
func TestInvoiceSecrets(t *testing.T) {
|
|
setup()
|
|
|
|
network := Regtest()
|
|
|
|
userKey, _ := NewHDPrivateKey(randomBytes(32), network)
|
|
userKey.Path = "m/schema:1'/recovery:1'"
|
|
muunKey, _ := NewHDPrivateKey(randomBytes(32), network)
|
|
muunKey.Path = "m/schema:1'/recovery:1'"
|
|
|
|
routeHints := &RouteHints{
|
|
Pubkey: "03c48d1ff96fa32e2776f71bba02102ffc2a1b91e2136586418607d32e762869fd",
|
|
FeeBaseMsat: 1000,
|
|
FeeProportionalMillionths: 1000,
|
|
CltvExpiryDelta: 8,
|
|
}
|
|
|
|
secrets, err := GenerateInvoiceSecrets(userKey.PublicKey(), muunKey.PublicKey())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if secrets.Length() != 5 {
|
|
t.Fatalf("expected 5 new secrets, got %d", secrets.Length())
|
|
}
|
|
|
|
err = PersistInvoiceSecrets(secrets)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Run("generating more invoices", func(t *testing.T) {
|
|
// Make sure the secrets list is already topped up
|
|
_, err := GenerateInvoiceSecrets(userKey.PublicKey(), muunKey.PublicKey())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// try to generate more secrets
|
|
moreSecrets, err := GenerateInvoiceSecrets(userKey.PublicKey(), muunKey.PublicKey())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if moreSecrets.Length() != 0 {
|
|
t.Fatal("expected no new secrets to be created")
|
|
}
|
|
})
|
|
|
|
t.Run("create an invoice", func(t *testing.T) {
|
|
builder := &InvoiceBuilder{}
|
|
builder.Network(network)
|
|
builder.UserKey(userKey)
|
|
builder.AddRouteHints(routeHints)
|
|
builder.AmountSat(1000)
|
|
builder.Description("hello world")
|
|
invoice, err := builder.Build()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if invoice == "" {
|
|
t.Fatal("expected non-empty invoice string")
|
|
}
|
|
|
|
payreq, err := zpay32.Decode(invoice, network.network)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !payreq.Features.HasFeature(lnwire.TLVOnionPayloadOptional) {
|
|
t.Fatal("expected invoice to have var onion optin feature")
|
|
}
|
|
if !payreq.Features.HasFeature(lnwire.PaymentAddrOptional) {
|
|
t.Fatal("expected invoice to have payment secret feature")
|
|
}
|
|
if payreq.MilliSat.ToSatoshis() != btcutil.Amount(1000) {
|
|
t.Fatalf("expected invoice amount to be 1000 sats, got %v", payreq.MilliSat)
|
|
}
|
|
if payreq.Description == nil || *payreq.Description != "hello world" {
|
|
t.Fatalf("expected payment description to match, got %v", payreq.Description)
|
|
}
|
|
if payreq.MinFinalCLTVExpiry() != 72 {
|
|
t.Fatalf("expected min final CLTV expiry to be 72, got %v", payreq.MinFinalCLTVExpiry())
|
|
}
|
|
if payreq.PaymentAddr == nil {
|
|
t.Fatalf("expected payment addr to be non-nil")
|
|
}
|
|
if len(payreq.RouteHints) == 0 {
|
|
t.Fatalf("expected invoice to contain route hints")
|
|
}
|
|
hopHints := payreq.RouteHints[0]
|
|
if len(hopHints) != 1 {
|
|
t.Fatalf("expected invoice route hints to contain exactly 1 hop hint")
|
|
}
|
|
if hopHints[0].ChannelID&(1<<63) == 0 {
|
|
t.Fatal("invalid short channel id in hophints")
|
|
}
|
|
if hopHints[0].FeeBaseMSat != 1000 {
|
|
t.Fatalf("expected fee base to be 1000 msat, got %v instead", hopHints[0].FeeBaseMSat)
|
|
}
|
|
if hopHints[0].FeeProportionalMillionths != 1000 {
|
|
t.Fatalf("expected fee proportional millionths to be 1000, got %v instead", hopHints[0].FeeProportionalMillionths)
|
|
}
|
|
if hopHints[0].CLTVExpiryDelta != 8 {
|
|
t.Fatalf("expected CLTV expiry delta to be 8, got %v instead", hopHints[0].CLTVExpiryDelta)
|
|
}
|
|
metadata, err := GetInvoiceMetadata(payreq.PaymentHash[:])
|
|
if err != nil {
|
|
t.Fatalf("expected invoice to contain metadata, got error: %v", err)
|
|
}
|
|
decryptedMetadata, err := userKey.Decrypter().Decrypt(metadata)
|
|
if err != nil {
|
|
t.Fatalf("expected metadata to decrypt correctly, got error: %v", err)
|
|
}
|
|
var jsonMetadata OperationMetadata
|
|
err = json.NewDecoder(bytes.NewReader(decryptedMetadata)).Decode(&jsonMetadata)
|
|
if err != nil {
|
|
t.Fatal("expected metadata to parse correctly")
|
|
}
|
|
if jsonMetadata.Invoice == "" {
|
|
t.Fatal("expected metadata to contain a non-empty invoice")
|
|
}
|
|
})
|
|
|
|
t.Run("creating a 2nd invoice returns a different payment hash", func(t *testing.T) {
|
|
|
|
builder := &InvoiceBuilder{}
|
|
builder.Network(network)
|
|
builder.UserKey(userKey)
|
|
builder.AddRouteHints(routeHints)
|
|
invoice1, err := builder.Build()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
payreq1, err := zpay32.Decode(invoice1, network.network)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
invoice2, err := builder.Build()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
payreq2, err := zpay32.Decode(invoice2, network.network)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if payreq1.PaymentHash == payreq2.PaymentHash {
|
|
t.Fatal("successive invoice payment hashes should be different")
|
|
}
|
|
|
|
})
|
|
|
|
t.Run("amountMsat gets stored", func(t *testing.T) {
|
|
builder := &InvoiceBuilder{}
|
|
builder.Network(network)
|
|
builder.UserKey(userKey)
|
|
builder.AddRouteHints(routeHints)
|
|
builder.AmountMSat(1001)
|
|
invoice3, err := builder.Build()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
payreq3, err := zpay32.Decode(invoice3, network.network)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
db, err := openDB()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
invoiceMetadata, err := db.FindByPaymentHash(payreq3.PaymentHash[:])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Note that we sent 1001 msats
|
|
if invoiceMetadata.AmountSat != 1 {
|
|
t.Fatalf("Expected persisted amount to 1 found %v", invoiceMetadata.AmountSat)
|
|
}
|
|
})
|
|
|
|
t.Run("two route hints are encoded", func(t *testing.T) {
|
|
builder := &InvoiceBuilder{}
|
|
invoice, err := builder.
|
|
Network(network).
|
|
UserKey(userKey).
|
|
AddRouteHints(routeHints).
|
|
AddRouteHints(&RouteHints{
|
|
Pubkey: "03c48d1ff96fa32e2776f71bba02102ffc2a1b91e2136586418607d32e762869ff",
|
|
FeeBaseMsat: 123,
|
|
FeeProportionalMillionths: 1,
|
|
CltvExpiryDelta: 23,
|
|
}).
|
|
Build()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
payreq, err := zpay32.Decode(invoice, network.network)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(payreq.RouteHints) != 2 {
|
|
t.Fatalf("Expected there to be 2 route hints, found %v", len(payreq.RouteHints))
|
|
}
|
|
|
|
for i, hops := range payreq.RouteHints {
|
|
if len(hops) != 1 {
|
|
t.Fatalf(
|
|
"Expected hops for hint %v to be 1, found %v",
|
|
i,
|
|
len(hops),
|
|
)
|
|
}
|
|
hint := hops[0]
|
|
|
|
var expectedFeeBase, expectedProportional uint32
|
|
var expectedPubKey string
|
|
if hint.CLTVExpiryDelta == 23 {
|
|
// Second hint
|
|
expectedFeeBase = 123
|
|
expectedProportional = 1
|
|
expectedPubKey = "03c48d1ff96fa32e2776f71bba02102ffc2a1b91e2136586418607d32e762869ff"
|
|
} else if hint.CLTVExpiryDelta == uint16(routeHints.CltvExpiryDelta) {
|
|
// First hint
|
|
expectedFeeBase = uint32(routeHints.FeeBaseMsat)
|
|
expectedProportional = uint32(routeHints.FeeProportionalMillionths)
|
|
expectedPubKey = routeHints.Pubkey
|
|
} else {
|
|
t.Fatalf("Failed to match route hint %v: %v", i, hops)
|
|
}
|
|
|
|
if hint.ChannelID&(1<<63) == 0 {
|
|
t.Fatal("invalid short channel id in hophints")
|
|
}
|
|
if hint.FeeProportionalMillionths != expectedProportional {
|
|
t.Fatalf("Route hint %v proportional fee %v != %v", i, hint.FeeProportionalMillionths, expectedProportional)
|
|
}
|
|
if hint.FeeBaseMSat != expectedFeeBase {
|
|
t.Fatalf("Route hint %v base fee %v != %v", i, hint.FeeBaseMSat, expectedFeeBase)
|
|
}
|
|
pubKey := hex.EncodeToString(hint.NodeID.SerializeCompressed())
|
|
if pubKey != expectedPubKey {
|
|
t.Fatalf("Route hint %v pub key %v != %v", i, pubKey, expectedPubKey)
|
|
}
|
|
}
|
|
})
|
|
|
|
}
|
|
|
|
func TestGetInvoiceMetadataMissingHash(t *testing.T) {
|
|
setup()
|
|
|
|
_, err := GetInvoiceMetadata(randomBytes(32))
|
|
if err == nil {
|
|
t.Fatal("expected GetInvoiceMetadata to fail")
|
|
}
|
|
}
|