mirror of
https://github.com/lightninglabs/loop
synced 2024-11-09 19:10:47 +00:00
191 lines
4.9 KiB
Go
191 lines
4.9 KiB
Go
package lsat
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"gopkg.in/macaroon.v2"
|
|
)
|
|
|
|
var (
|
|
// zeroPreimage is an empty, invalid payment preimage that is used to
|
|
// initialize pending tokens with.
|
|
zeroPreimage lntypes.Preimage
|
|
)
|
|
|
|
// Token is the main type to store an LSAT token in.
|
|
type Token struct {
|
|
// PaymentHash is the hash of the LSAT invoice that needs to be paid.
|
|
// Knowing the preimage to this hash is seen as proof of payment by the
|
|
// authentication server.
|
|
PaymentHash lntypes.Hash
|
|
|
|
// Preimage is the proof of payment indicating that the token has been
|
|
// paid for if set. If the preimage is empty, the payment might still
|
|
// be in transit.
|
|
Preimage lntypes.Preimage
|
|
|
|
// AmountPaid is the total amount in msat that the user paid to get the
|
|
// token. This does not include routing fees.
|
|
AmountPaid lnwire.MilliSatoshi
|
|
|
|
// RoutingFeePaid is the total amount in msat that the user paid in
|
|
// routing fee to get the token.
|
|
RoutingFeePaid lnwire.MilliSatoshi
|
|
|
|
// TimeCreated is the moment when this token was created.
|
|
TimeCreated time.Time
|
|
|
|
// baseMac is the base macaroon in its original form as baked by the
|
|
// authentication server. No client side caveats have been added to it
|
|
// yet.
|
|
baseMac *macaroon.Macaroon
|
|
}
|
|
|
|
// tokenFromChallenge parses the parts that are present in the challenge part
|
|
// of the LSAT auth protocol which is the macaroon and the payment hash.
|
|
func tokenFromChallenge(baseMac []byte, paymentHash *[32]byte) (*Token, error) {
|
|
// First, validate that the macaroon is valid and can be unmarshaled.
|
|
mac := &macaroon.Macaroon{}
|
|
err := mac.UnmarshalBinary(baseMac)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to unmarshal macaroon: %v", err)
|
|
}
|
|
|
|
token := &Token{
|
|
TimeCreated: time.Now(),
|
|
baseMac: mac,
|
|
Preimage: zeroPreimage,
|
|
}
|
|
hash, err := lntypes.MakeHash(paymentHash[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
token.PaymentHash = hash
|
|
return token, nil
|
|
}
|
|
|
|
// BaseMacaroon returns the base macaroon as received from the authentication
|
|
// server.
|
|
func (t *Token) BaseMacaroon() *macaroon.Macaroon {
|
|
return t.baseMac.Clone()
|
|
}
|
|
|
|
// PaidMacaroon returns the base macaroon with the proof of payment (preimage)
|
|
// added as a first-party-caveat.
|
|
func (t *Token) PaidMacaroon() (*macaroon.Macaroon, error) {
|
|
mac := t.BaseMacaroon()
|
|
err := AddFirstPartyCaveats(
|
|
mac, NewCaveat(PreimageKey, t.Preimage.String()),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return mac, nil
|
|
}
|
|
|
|
// IsValid returns true if the timestamp contained in the base macaroon is not
|
|
// yet expired.
|
|
func (t *Token) IsValid() bool {
|
|
// TODO(guggero): Extract and validate from caveat once we add an
|
|
// expiration date to the LSAT.
|
|
return true
|
|
}
|
|
|
|
// isPending returns true if the payment for the LSAT is still in flight and we
|
|
// haven't received the preimage yet.
|
|
func (t *Token) isPending() bool {
|
|
return t.Preimage == zeroPreimage
|
|
}
|
|
|
|
// serializeToken returns a byte-serialized representation of the token.
|
|
func serializeToken(t *Token) ([]byte, error) {
|
|
var b bytes.Buffer
|
|
|
|
baseMacBytes, err := t.baseMac.MarshalBinary()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
macLen := uint32(len(baseMacBytes))
|
|
if err := binary.Write(&b, byteOrder, macLen); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Write(&b, byteOrder, baseMacBytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Write(&b, byteOrder, t.PaymentHash); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Write(&b, byteOrder, t.Preimage); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Write(&b, byteOrder, t.AmountPaid); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Write(&b, byteOrder, t.RoutingFeePaid); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
timeUnix := t.TimeCreated.UnixNano()
|
|
if err := binary.Write(&b, byteOrder, timeUnix); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
// deserializeToken constructs a token by reading it from a byte slice.
|
|
func deserializeToken(value []byte) (*Token, error) {
|
|
r := bytes.NewReader(value)
|
|
|
|
var macLen uint32
|
|
if err := binary.Read(r, byteOrder, &macLen); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
macBytes := make([]byte, macLen)
|
|
if err := binary.Read(r, byteOrder, &macBytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var paymentHash [lntypes.HashSize]byte
|
|
if err := binary.Read(r, byteOrder, &paymentHash); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
token, err := tokenFromChallenge(macBytes, &paymentHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Read(r, byteOrder, &token.Preimage); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Read(r, byteOrder, &token.AmountPaid); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Read(r, byteOrder, &token.RoutingFeePaid); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var unixNano int64
|
|
if err := binary.Read(r, byteOrder, &unixNano); err != nil {
|
|
return nil, err
|
|
}
|
|
token.TimeCreated = time.Unix(0, unixNano)
|
|
|
|
return token, nil
|
|
}
|