2
0
mirror of https://github.com/lightninglabs/loop synced 2024-11-09 19:10:47 +00:00
loop/instantout/reservation/reservation.go
2024-01-31 16:01:04 +01:00

180 lines
4.1 KiB
Go

package reservation
import (
"bytes"
"context"
"errors"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/fsm"
reservation_script "github.com/lightninglabs/loop/instantout/reservation/script"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
)
// ID is a unique identifier for a reservation.
type ID [IdLength]byte
// FromByteSlice creates a reservation id from a byte slice.
func (r *ID) FromByteSlice(b []byte) error {
if len(b) != IdLength {
return fmt.Errorf("reservation id must be 32 bytes, got %d, %x",
len(b), b)
}
copy(r[:], b)
return nil
}
// Reservation holds all the necessary information for the 2-of-2 multisig
// reservation utxo.
type Reservation struct {
// ID is the unique identifier of the reservation.
ID ID
// State is the current state of the reservation.
State fsm.StateType
// ClientPubkey is the client's pubkey.
ClientPubkey *btcec.PublicKey
// ServerPubkey is the server's pubkey.
ServerPubkey *btcec.PublicKey
// Value is the amount of the reservation.
Value btcutil.Amount
// Expiry is the absolute block height at which the reservation expires.
Expiry uint32
// KeyLocator is the key locator of the client's key.
KeyLocator keychain.KeyLocator
// Outpoint is the outpoint of the reservation.
Outpoint *wire.OutPoint
// InitiationHeight is the height at which the reservation was
// initiated.
InitiationHeight int32
// ConfirmationHeight is the height at which the reservation was
// confirmed.
ConfirmationHeight uint32
}
func NewReservation(id ID, serverPubkey, clientPubkey *btcec.PublicKey,
value btcutil.Amount, expiry, heightHint uint32,
keyLocator keychain.KeyLocator) (*Reservation,
error) {
if id == [32]byte{} {
return nil, errors.New("id is empty")
}
if clientPubkey == nil {
return nil, errors.New("client pubkey is nil")
}
if serverPubkey == nil {
return nil, errors.New("server pubkey is nil")
}
if expiry == 0 {
return nil, errors.New("expiry is 0")
}
if value == 0 {
return nil, errors.New("value is 0")
}
if keyLocator.Family == 0 {
return nil, errors.New("key locator family is 0")
}
return &Reservation{
ID: id,
Value: value,
ClientPubkey: clientPubkey,
ServerPubkey: serverPubkey,
KeyLocator: keyLocator,
Expiry: expiry,
InitiationHeight: int32(heightHint),
}, nil
}
// GetPkScript returns the pk script of the reservation.
func (r *Reservation) GetPkScript() ([]byte, error) {
// Now that we have all the required data, we can create the pk script.
pkScript, err := reservation_script.ReservationScript(
r.Expiry, r.ServerPubkey, r.ClientPubkey,
)
if err != nil {
return nil, err
}
return pkScript, nil
}
// Output returns the reservation output.
func (r *Reservation) Output() (*wire.TxOut, error) {
pkscript, err := r.GetPkScript()
if err != nil {
return nil, err
}
return wire.NewTxOut(int64(r.Value), pkscript), nil
}
func (r *Reservation) findReservationOutput(tx *wire.MsgTx) (*wire.OutPoint,
error) {
pkScript, err := r.GetPkScript()
if err != nil {
return nil, err
}
for i, txOut := range tx.TxOut {
if bytes.Equal(txOut.PkScript, pkScript) {
return &wire.OutPoint{
Hash: tx.TxHash(),
Index: uint32(i),
}, nil
}
}
return nil, errors.New("reservation output not found")
}
// Musig2CreateSession creates a musig2 session for the reservation.
func (r *Reservation) Musig2CreateSession(ctx context.Context,
signer lndclient.SignerClient) (*input.MuSig2SessionInfo, error) {
signers := [][]byte{
r.ClientPubkey.SerializeCompressed(),
r.ServerPubkey.SerializeCompressed(),
}
expiryLeaf, err := reservation_script.TaprootExpiryScript(
r.Expiry, r.ServerPubkey,
)
if err != nil {
return nil, err
}
rootHash := expiryLeaf.TapHash()
musig2SessionInfo, err := signer.MuSig2CreateSession(
ctx, input.MuSig2Version100RC2, &r.KeyLocator, signers,
lndclient.MuSig2TaprootTweakOpt(rootHash[:], false),
)
if err != nil {
return nil, err
}
return musig2SessionInfo, nil
}