You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
loop/lsat/token.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
}