mirror of
https://github.com/lightninglabs/loop
synced 2024-11-09 19:10:47 +00:00
180 lines
4.1 KiB
Go
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
|
|
}
|