mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
staticutil: refactor methods into utils
This commit is contained in:
parent
e77dc53760
commit
a918842374
8 changed files with 505 additions and 71 deletions
|
|
@ -76,6 +76,17 @@ func (m *mockStaticAddressClient) ServerWithdrawDeposits(ctx context.Context,
|
|||
args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockStaticAddressClient) ServerPsbtWithdrawDeposits(ctx context.Context,
|
||||
in *swapserverrpc.ServerPsbtWithdrawRequest,
|
||||
opts ...grpc.CallOption) (*swapserverrpc.ServerPsbtWithdrawResponse,
|
||||
error) {
|
||||
|
||||
args := m.Called(ctx, in, opts)
|
||||
|
||||
return args.Get(0).(*swapserverrpc.ServerPsbtWithdrawResponse),
|
||||
args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockStaticAddressClient) ServerNewAddress(ctx context.Context,
|
||||
in *swapserverrpc.ServerNewAddressRequest, opts ...grpc.CallOption) (
|
||||
*swapserverrpc.ServerNewAddressResponse, error) {
|
||||
|
|
|
|||
|
|
@ -93,6 +93,17 @@ func (m *mockStaticAddressClient) ServerWithdrawDeposits(ctx context.Context,
|
|||
args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockStaticAddressClient) ServerPsbtWithdrawDeposits(ctx context.Context,
|
||||
in *swapserverrpc.ServerPsbtWithdrawRequest,
|
||||
opts ...grpc.CallOption) (*swapserverrpc.ServerPsbtWithdrawResponse,
|
||||
error) {
|
||||
|
||||
args := m.Called(ctx, in, opts)
|
||||
|
||||
return args.Get(0).(*swapserverrpc.ServerPsbtWithdrawResponse),
|
||||
args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockStaticAddressClient) ServerNewAddress(ctx context.Context,
|
||||
in *swapserverrpc.ServerNewAddressRequest, opts ...grpc.CallOption) (
|
||||
*swapserverrpc.ServerNewAddressResponse, error) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/lightninglabs/loop"
|
||||
"github.com/lightninglabs/loop/fsm"
|
||||
"github.com/lightninglabs/loop/staticaddr/deposit"
|
||||
"github.com/lightninglabs/loop/staticaddr/staticutil"
|
||||
"github.com/lightninglabs/loop/staticaddr/version"
|
||||
"github.com/lightninglabs/loop/swap"
|
||||
"github.com/lightninglabs/loop/swapserverrpc"
|
||||
|
|
@ -318,8 +319,11 @@ func (f *FSM) SignHtlcTxAction(ctx context.Context,
|
|||
|
||||
// Create a musig2 session for each deposit and different htlc tx fee
|
||||
// rates.
|
||||
createSession := f.loopIn.createMusig2Sessions
|
||||
htlcSessions, clientHtlcNonces, err := createSession(ctx, f.cfg.Signer)
|
||||
createSession := staticutil.CreateMusig2Sessions
|
||||
htlcSessions, clientHtlcNonces, err := createSession(
|
||||
ctx, f.cfg.Signer, f.loopIn.Deposits, f.loopIn.AddressParams,
|
||||
f.loopIn.Address,
|
||||
)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to create musig2 sessions: %w", err)
|
||||
|
||||
|
|
@ -328,7 +332,8 @@ func (f *FSM) SignHtlcTxAction(ctx context.Context,
|
|||
defer f.cleanUpSessions(ctx, htlcSessions)
|
||||
|
||||
htlcSessionsHighFee, highFeeNonces, err := createSession(
|
||||
ctx, f.cfg.Signer,
|
||||
ctx, f.cfg.Signer, f.loopIn.Deposits, f.loopIn.AddressParams,
|
||||
f.loopIn.Address,
|
||||
)
|
||||
if err != nil {
|
||||
return f.HandleError(err)
|
||||
|
|
@ -336,7 +341,8 @@ func (f *FSM) SignHtlcTxAction(ctx context.Context,
|
|||
defer f.cleanUpSessions(ctx, htlcSessionsHighFee)
|
||||
|
||||
htlcSessionsExtremelyHighFee, extremelyHighNonces, err := createSession(
|
||||
ctx, f.cfg.Signer,
|
||||
ctx, f.cfg.Signer, f.loopIn.Deposits, f.loopIn.AddressParams,
|
||||
f.loopIn.Address,
|
||||
)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to convert nonces: %w", err)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/lightninglabs/loop/staticaddr/address"
|
||||
"github.com/lightninglabs/loop/staticaddr/deposit"
|
||||
"github.com/lightninglabs/loop/staticaddr/script"
|
||||
"github.com/lightninglabs/loop/staticaddr/staticutil"
|
||||
"github.com/lightninglabs/loop/staticaddr/version"
|
||||
"github.com/lightninglabs/loop/swap"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
|
|
@ -169,47 +170,6 @@ func (l *StaticAddressLoopIn) getHtlc(chainParams *chaincfg.Params) (*swap.Htlc,
|
|||
)
|
||||
}
|
||||
|
||||
// createMusig2Sessions creates a musig2 session for a number of deposits.
|
||||
func (l *StaticAddressLoopIn) createMusig2Sessions(ctx context.Context,
|
||||
signer lndclient.SignerClient) ([]*input.MuSig2SessionInfo, [][]byte,
|
||||
error) {
|
||||
|
||||
musig2Sessions := make([]*input.MuSig2SessionInfo, len(l.Deposits))
|
||||
clientNonces := make([][]byte, len(l.Deposits))
|
||||
|
||||
// Create the sessions and nonces from the deposits.
|
||||
for i := 0; i < len(l.Deposits); i++ {
|
||||
session, err := l.createMusig2Session(ctx, signer)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
musig2Sessions[i] = session
|
||||
clientNonces[i] = session.PublicNonce[:]
|
||||
}
|
||||
|
||||
return musig2Sessions, clientNonces, nil
|
||||
}
|
||||
|
||||
// Musig2CreateSession creates a musig2 session for the deposit.
|
||||
func (l *StaticAddressLoopIn) createMusig2Session(ctx context.Context,
|
||||
signer lndclient.SignerClient) (*input.MuSig2SessionInfo, error) {
|
||||
|
||||
signers := [][]byte{
|
||||
l.AddressParams.ClientPubkey.SerializeCompressed(),
|
||||
l.AddressParams.ServerPubkey.SerializeCompressed(),
|
||||
}
|
||||
|
||||
expiryLeaf := l.Address.TimeoutLeaf
|
||||
|
||||
rootHash := expiryLeaf.TapHash()
|
||||
|
||||
return signer.MuSig2CreateSession(
|
||||
ctx, input.MuSig2Version100RC2, &l.AddressParams.KeyLocator,
|
||||
signers, lndclient.MuSig2TaprootTweakOpt(rootHash[:], false),
|
||||
)
|
||||
}
|
||||
|
||||
// signMusig2Tx adds the server nonces to the musig2 sessions and signs the
|
||||
// transaction.
|
||||
func (l *StaticAddressLoopIn) signMusig2Tx(ctx context.Context,
|
||||
|
|
@ -217,7 +177,9 @@ func (l *StaticAddressLoopIn) signMusig2Tx(ctx context.Context,
|
|||
musig2sessions []*input.MuSig2SessionInfo,
|
||||
counterPartyNonces [][musig2.PubNonceSize]byte) ([][]byte, error) {
|
||||
|
||||
prevOuts, err := l.toPrevOuts(l.Deposits, l.AddressParams.PkScript)
|
||||
prevOuts, err := staticutil.ToPrevOuts(
|
||||
l.Deposits, l.AddressParams.PkScript,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -523,29 +485,6 @@ func (l *StaticAddressLoopIn) Outpoints() []wire.OutPoint {
|
|||
return outpoints
|
||||
}
|
||||
|
||||
func (l *StaticAddressLoopIn) toPrevOuts(deposits []*deposit.Deposit,
|
||||
pkScript []byte) (map[wire.OutPoint]*wire.TxOut, error) {
|
||||
|
||||
prevOuts := make(map[wire.OutPoint]*wire.TxOut, len(deposits))
|
||||
for _, d := range deposits {
|
||||
outpoint := wire.OutPoint{
|
||||
Hash: d.Hash,
|
||||
Index: d.Index,
|
||||
}
|
||||
txOut := &wire.TxOut{
|
||||
Value: int64(d.Value),
|
||||
PkScript: pkScript,
|
||||
}
|
||||
if _, ok := prevOuts[outpoint]; ok {
|
||||
return nil, fmt.Errorf("duplicate outpoint %v",
|
||||
outpoint)
|
||||
}
|
||||
prevOuts[outpoint] = txOut
|
||||
}
|
||||
|
||||
return prevOuts, nil
|
||||
}
|
||||
|
||||
// GetState returns the current state of the loop-in swap.
|
||||
func (l *StaticAddressLoopIn) GetState() fsm.StateType {
|
||||
l.mu.Lock()
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/lightninglabs/loop/labels"
|
||||
"github.com/lightninglabs/loop/staticaddr/address"
|
||||
"github.com/lightninglabs/loop/staticaddr/deposit"
|
||||
"github.com/lightninglabs/loop/staticaddr/staticutil"
|
||||
"github.com/lightninglabs/loop/swapserverrpc"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
|
|
@ -391,8 +392,8 @@ func (m *Manager) handleLoopInSweepReq(ctx context.Context,
|
|||
)
|
||||
|
||||
copy(serverNonce[:], nonce)
|
||||
musig2Session, err := loopIn.createMusig2Session(
|
||||
ctx, m.cfg.Signer,
|
||||
musig2Session, err := staticutil.CreateMusig2Session(
|
||||
ctx, m.cfg.Signer, loopIn.AddressParams, loopIn.Address,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
25
staticaddr/staticutil/outpoints.go
Normal file
25
staticaddr/staticutil/outpoints.go
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package staticutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
)
|
||||
|
||||
// ToWireOutpoints converts lnrpc.OutPoint protos into wire.OutPoint structs so
|
||||
// they can be consumed by lower level transaction building code.
|
||||
func ToWireOutpoints(outpoints []*lnrpc.OutPoint) ([]wire.OutPoint, error) {
|
||||
serverOutpoints := make([]wire.OutPoint, 0, len(outpoints))
|
||||
for _, o := range outpoints {
|
||||
outpointStr := fmt.Sprintf("%s:%d", o.TxidStr, o.OutputIndex)
|
||||
newOutpoint, err := wire.NewOutPointFromString(outpointStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverOutpoints = append(serverOutpoints, *newOutpoint)
|
||||
}
|
||||
|
||||
return serverOutpoints, nil
|
||||
}
|
||||
205
staticaddr/staticutil/utils.go
Normal file
205
staticaddr/staticutil/utils.go
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
package staticutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/lightninglabs/loop/staticaddr/address"
|
||||
"github.com/lightninglabs/loop/staticaddr/deposit"
|
||||
"github.com/lightninglabs/loop/staticaddr/script"
|
||||
"github.com/lightninglabs/loop/swapserverrpc"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
)
|
||||
|
||||
// ToPrevOuts converts a slice of deposits to a map of outpoints to TxOuts.
|
||||
func ToPrevOuts(deposits []*deposit.Deposit,
|
||||
pkScript []byte) (map[wire.OutPoint]*wire.TxOut, error) {
|
||||
|
||||
prevOuts := make(map[wire.OutPoint]*wire.TxOut, len(deposits))
|
||||
for _, d := range deposits {
|
||||
outpoint := wire.OutPoint{
|
||||
Hash: d.Hash,
|
||||
Index: d.Index,
|
||||
}
|
||||
txOut := &wire.TxOut{
|
||||
Value: int64(d.Value),
|
||||
PkScript: pkScript,
|
||||
}
|
||||
if _, ok := prevOuts[outpoint]; ok {
|
||||
return nil, fmt.Errorf("duplicate outpoint %v",
|
||||
outpoint)
|
||||
}
|
||||
prevOuts[outpoint] = txOut
|
||||
}
|
||||
|
||||
return prevOuts, nil
|
||||
}
|
||||
|
||||
// CreateMusig2Sessions creates a musig2 session for a number of deposits.
|
||||
func CreateMusig2Sessions(ctx context.Context,
|
||||
signer lndclient.SignerClient, deposits []*deposit.Deposit,
|
||||
addrParams *address.Parameters,
|
||||
staticAddress *script.StaticAddress) ([]*input.MuSig2SessionInfo,
|
||||
[][]byte, error) {
|
||||
|
||||
musig2Sessions := make([]*input.MuSig2SessionInfo, len(deposits))
|
||||
clientNonces := make([][]byte, len(deposits))
|
||||
|
||||
// Create the sessions and nonces from the deposits.
|
||||
for i := 0; i < len(deposits); i++ {
|
||||
session, err := CreateMusig2Session(
|
||||
ctx, signer, addrParams, staticAddress,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
musig2Sessions[i] = session
|
||||
clientNonces[i] = session.PublicNonce[:]
|
||||
}
|
||||
|
||||
return musig2Sessions, clientNonces, nil
|
||||
}
|
||||
|
||||
// CreateMusig2SessionsPerDeposit creates a musig2 session for a number of
|
||||
// deposits.
|
||||
func CreateMusig2SessionsPerDeposit(ctx context.Context,
|
||||
signer lndclient.SignerClient, deposits []*deposit.Deposit,
|
||||
addrParams *address.Parameters,
|
||||
staticAddress *script.StaticAddress) (
|
||||
map[string]*input.MuSig2SessionInfo, map[string][]byte, map[string]int,
|
||||
error) {
|
||||
|
||||
sessions := make(map[string]*input.MuSig2SessionInfo)
|
||||
nonces := make(map[string][]byte)
|
||||
depositToIdx := make(map[string]int)
|
||||
|
||||
// Create the musig2 sessions for the sweepless sweep tx.
|
||||
for i, deposit := range deposits {
|
||||
session, err := CreateMusig2Session(
|
||||
ctx, signer, addrParams, staticAddress,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
sessions[deposit.String()] = session
|
||||
nonces[deposit.String()] = session.PublicNonce[:]
|
||||
depositToIdx[deposit.String()] = i
|
||||
}
|
||||
|
||||
return sessions, nonces, depositToIdx, nil
|
||||
}
|
||||
|
||||
// CreateMusig2Session creates a musig2 session for the deposit.
|
||||
func CreateMusig2Session(ctx context.Context,
|
||||
signer lndclient.SignerClient, addrParams *address.Parameters,
|
||||
staticAddress *script.StaticAddress) (*input.MuSig2SessionInfo, error) {
|
||||
|
||||
signers := [][]byte{
|
||||
addrParams.ClientPubkey.SerializeCompressed(),
|
||||
addrParams.ServerPubkey.SerializeCompressed(),
|
||||
}
|
||||
|
||||
expiryLeaf := staticAddress.TimeoutLeaf
|
||||
|
||||
rootHash := expiryLeaf.TapHash()
|
||||
|
||||
return signer.MuSig2CreateSession(
|
||||
ctx, input.MuSig2Version100RC2, &addrParams.KeyLocator,
|
||||
signers, lndclient.MuSig2TaprootTweakOpt(rootHash[:], false),
|
||||
)
|
||||
}
|
||||
|
||||
// GetPrevoutInfo converts a map of prevOuts to protobuf.
|
||||
func GetPrevoutInfo(prevOuts map[wire.OutPoint]*wire.TxOut,
|
||||
) []*swapserverrpc.PrevoutInfo {
|
||||
|
||||
prevoutInfos := make([]*swapserverrpc.PrevoutInfo, 0, len(prevOuts))
|
||||
|
||||
for outpoint, txOut := range prevOuts {
|
||||
prevoutInfo := &swapserverrpc.PrevoutInfo{
|
||||
TxidBytes: outpoint.Hash[:],
|
||||
OutputIndex: outpoint.Index,
|
||||
Value: uint64(txOut.Value),
|
||||
PkScript: txOut.PkScript,
|
||||
}
|
||||
prevoutInfos = append(prevoutInfos, prevoutInfo)
|
||||
}
|
||||
|
||||
// Sort UTXOs by txid:index using BIP-0069 rule. The function is used
|
||||
// in unit tests a lot, and it is useful to make it deterministic.
|
||||
sort.Slice(prevoutInfos, func(i, j int) bool {
|
||||
return bip69inputLess(prevoutInfos[i], prevoutInfos[j])
|
||||
})
|
||||
|
||||
return prevoutInfos
|
||||
}
|
||||
|
||||
// bip69inputLess returns true if input1 < input2 according to BIP-0069
|
||||
// First sort based on input hash (reversed / rpc-style), then index.
|
||||
// The code is based on btcd/btcutil/txsort/txsort.go.
|
||||
func bip69inputLess(input1, input2 *swapserverrpc.PrevoutInfo) bool {
|
||||
// Input hashes are the same, so compare the index.
|
||||
var ihash, jhash chainhash.Hash
|
||||
copy(ihash[:], input1.TxidBytes)
|
||||
copy(jhash[:], input2.TxidBytes)
|
||||
if ihash == jhash {
|
||||
return input1.OutputIndex < input2.OutputIndex
|
||||
}
|
||||
|
||||
// At this point, the hashes are not equal, so reverse them to
|
||||
// big-endian and return the result of the comparison.
|
||||
const hashSize = chainhash.HashSize
|
||||
for b := 0; b < hashSize/2; b++ {
|
||||
ihash[b], ihash[hashSize-1-b] = ihash[hashSize-1-b], ihash[b]
|
||||
jhash[b], jhash[hashSize-1-b] = jhash[hashSize-1-b], jhash[b]
|
||||
}
|
||||
return bytes.Compare(ihash[:], jhash[:]) == -1
|
||||
}
|
||||
|
||||
// SelectDeposits sorts the deposits by amount in descending order. It then
|
||||
// selects the deposits that are needed to cover the amount requested without
|
||||
// leaving a dust change. It returns an error if the sum of deposits minus dust
|
||||
// is less than the requested amount.
|
||||
func SelectDeposits(deposits []*deposit.Deposit, amount int64) (
|
||||
[]*deposit.Deposit, error) {
|
||||
|
||||
// Check that sum of deposits covers the swap amount while leaving no
|
||||
// dust change.
|
||||
dustLimit := lnwallet.DustLimitForSize(input.P2TRSize)
|
||||
var depositSum btcutil.Amount
|
||||
for _, deposit := range deposits {
|
||||
depositSum += deposit.Value
|
||||
}
|
||||
if depositSum-dustLimit < btcutil.Amount(amount) {
|
||||
return nil, fmt.Errorf("insufficient funds to cover swap " +
|
||||
"amount, try manually selecting deposits")
|
||||
}
|
||||
|
||||
// Sort the deposits by amount in descending order.
|
||||
sort.Slice(deposits, func(i, j int) bool {
|
||||
return deposits[i].Value > deposits[j].Value
|
||||
})
|
||||
|
||||
// Select the deposits that are needed to cover the swap amount without
|
||||
// leaving a dust change.
|
||||
var selectedDeposits []*deposit.Deposit
|
||||
var selectedAmount btcutil.Amount
|
||||
for _, deposit := range deposits {
|
||||
if selectedAmount >= btcutil.Amount(amount)+dustLimit {
|
||||
break
|
||||
}
|
||||
selectedDeposits = append(selectedDeposits, deposit)
|
||||
selectedAmount += deposit.Value
|
||||
}
|
||||
|
||||
return selectedDeposits, nil
|
||||
}
|
||||
236
staticaddr/staticutil/utils_test.go
Normal file
236
staticaddr/staticutil/utils_test.go
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
package staticutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/loop/staticaddr/address"
|
||||
"github.com/lightninglabs/loop/staticaddr/deposit"
|
||||
"github.com/lightninglabs/loop/staticaddr/script"
|
||||
"github.com/lightninglabs/loop/swapserverrpc"
|
||||
looptest "github.com/lightninglabs/loop/test"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// mustHash converts a hex string to a chainhash.Hash and panics on error.
|
||||
func mustHash(t *testing.T, s string) chainhash.Hash {
|
||||
t.Helper()
|
||||
h, err := chainhash.NewHashFromStr(s)
|
||||
require.NoError(t, err)
|
||||
return *h
|
||||
}
|
||||
|
||||
func TestToPrevOuts_Success(t *testing.T) {
|
||||
// Prepare two distinct deposits with different outpoints and values.
|
||||
d1 := &deposit.Deposit{
|
||||
OutPoint: wire.OutPoint{
|
||||
Hash: mustHash(t, "0000000000000000000000000000000000000000000000000000000000000001"),
|
||||
Index: 0,
|
||||
},
|
||||
Value: btcutil.Amount(12345),
|
||||
}
|
||||
|
||||
d2 := &deposit.Deposit{
|
||||
OutPoint: wire.OutPoint{
|
||||
Hash: mustHash(t, "1111111111111111111111111111111111111111111111111111111111111111"),
|
||||
Index: 7,
|
||||
},
|
||||
Value: btcutil.Amount(987654321),
|
||||
}
|
||||
|
||||
pkScript := []byte{0x51, 0x21, 0x02, 0x52} // arbitrary bytes
|
||||
|
||||
prevOuts, err := ToPrevOuts([]*deposit.Deposit{d1, d2}, pkScript)
|
||||
require.NoError(t, err)
|
||||
|
||||
// We expect two entries.
|
||||
require.Len(t, prevOuts, 2)
|
||||
|
||||
// Check the first outpoint mapping.
|
||||
txOut1, ok := prevOuts[d1.OutPoint]
|
||||
require.True(t, ok, "expected outpoint d1 to be present")
|
||||
require.EqualValues(t, int64(d1.Value), txOut1.Value)
|
||||
require.Equal(t, pkScript, txOut1.PkScript)
|
||||
|
||||
// Check the second outpoint mapping.
|
||||
txOut2, ok := prevOuts[d2.OutPoint]
|
||||
require.True(t, ok, "expected outpoint d2 to be present")
|
||||
require.EqualValues(t, int64(d2.Value), txOut2.Value)
|
||||
require.Equal(t, pkScript, txOut2.PkScript)
|
||||
|
||||
// Ensure the keys in the map are exactly the outpoints we provided.
|
||||
for op := range prevOuts {
|
||||
require.True(t, op == d1.OutPoint || op == d2.OutPoint)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToPrevOuts_DuplicateOutpoint(t *testing.T) {
|
||||
// Two deposits that share the exact same outpoint should cause an error.
|
||||
shared := wire.OutPoint{
|
||||
Hash: mustHash(t, "2222222222222222222222222222222222222222222222222222222222222222"),
|
||||
Index: 2,
|
||||
}
|
||||
|
||||
d1 := &deposit.Deposit{OutPoint: shared, Value: btcutil.Amount(100)}
|
||||
d2 := &deposit.Deposit{OutPoint: shared, Value: btcutil.Amount(200)}
|
||||
|
||||
_, err := ToPrevOuts([]*deposit.Deposit{d1, d2}, []byte{0x00})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestGetPrevoutInfo_ConversionAndSorting(t *testing.T) {
|
||||
// Helper to create a hash from string.
|
||||
must := func(s string) chainhash.Hash {
|
||||
h, err := chainhash.NewHashFromStr(s)
|
||||
require.NoError(t, err)
|
||||
return *h
|
||||
}
|
||||
|
||||
// Choose txids such that after reversal, ordering is determined by the
|
||||
// last byte of the original hex string.
|
||||
txidA := must("0000000000000000000000000000000000000000000000000000000000000001")
|
||||
txidB := must("0000000000000000000000000000000000000000000000000000000000000002")
|
||||
|
||||
pkScript := []byte{0xaa, 0xbb}
|
||||
|
||||
prevOuts := map[wire.OutPoint]*wire.TxOut{
|
||||
{Hash: txidA, Index: 5}: {Value: 11, PkScript: pkScript},
|
||||
{Hash: txidA, Index: 2}: {Value: 22, PkScript: pkScript},
|
||||
{Hash: txidB, Index: 0}: {Value: 33, PkScript: pkScript},
|
||||
}
|
||||
|
||||
infos := GetPrevoutInfo(prevOuts)
|
||||
|
||||
// Expect deterministic ordering:
|
||||
// 1) All entries with txidA (..01) before txidB (..02) due to BIP-69
|
||||
// compare on reversed hashes.
|
||||
// 2) Within txidA, index 2 before index 5.
|
||||
require.Len(t, infos, 3)
|
||||
|
||||
require.Equal(t, &swapserverrpc.PrevoutInfo{
|
||||
TxidBytes: txidA[:],
|
||||
OutputIndex: 2,
|
||||
Value: 22,
|
||||
PkScript: pkScript,
|
||||
}, infos[0])
|
||||
|
||||
require.Equal(t, &swapserverrpc.PrevoutInfo{
|
||||
TxidBytes: txidA[:],
|
||||
OutputIndex: 5,
|
||||
Value: 11,
|
||||
PkScript: pkScript,
|
||||
}, infos[1])
|
||||
|
||||
require.Equal(t, &swapserverrpc.PrevoutInfo{
|
||||
TxidBytes: txidB[:],
|
||||
OutputIndex: 0,
|
||||
Value: 33,
|
||||
PkScript: pkScript,
|
||||
}, infos[2])
|
||||
}
|
||||
|
||||
func TestBip69InputLess_SameHashIndexOrder(t *testing.T) {
|
||||
txid := make([]byte, 32)
|
||||
txid[31] = 0x7f // Arbitrary value.
|
||||
|
||||
a := &swapserverrpc.PrevoutInfo{TxidBytes: txid, OutputIndex: 1}
|
||||
b := &swapserverrpc.PrevoutInfo{TxidBytes: txid, OutputIndex: 3}
|
||||
|
||||
require.True(t, bip69inputLess(a, b))
|
||||
require.False(t, bip69inputLess(b, a))
|
||||
}
|
||||
|
||||
func TestBip69InputLess_DifferentHashes(t *testing.T) {
|
||||
// txid1 ends with 0x01, txid2 ends with 0x02. After reversing for
|
||||
// comparison, txid1 should still come before txid2 in lexicographic
|
||||
// order.
|
||||
h1, _ := chainhash.NewHashFromStr("0000000000000000000000000000000000000000000000000000000000000001")
|
||||
h2, _ := chainhash.NewHashFromStr("0000000000000000000000000000000000000000000000000000000000000002")
|
||||
|
||||
a := &swapserverrpc.PrevoutInfo{TxidBytes: h1[:], OutputIndex: 9}
|
||||
b := &swapserverrpc.PrevoutInfo{TxidBytes: h2[:], OutputIndex: 0}
|
||||
|
||||
require.True(t, bip69inputLess(a, b))
|
||||
require.False(t, bip69inputLess(b, a))
|
||||
}
|
||||
|
||||
func TestCreateMusig2Session_Success(t *testing.T) {
|
||||
// Set up mock signer from loop/test package.
|
||||
lnd := looptest.NewMockLnd()
|
||||
signer := lnd.Signer
|
||||
|
||||
// Create dummy key material for address parameters.
|
||||
clientKey, err := btcec.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
serverKey, err := btcec.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
params := &address.Parameters{
|
||||
ClientPubkey: clientKey.PubKey(),
|
||||
ServerPubkey: serverKey.PubKey(),
|
||||
Expiry: 10,
|
||||
PkScript: []byte{0x51},
|
||||
KeyLocator: keychain.KeyLocator{Family: 1, Index: 2},
|
||||
}
|
||||
|
||||
// Build a static address for tweak options.
|
||||
staticAddr, err := script.NewStaticAddress(
|
||||
input.MuSig2Version100RC2, int64(params.Expiry), params.ClientPubkey, params.ServerPubkey,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
sess, err := CreateMusig2Session(context.Background(), signer, params, staticAddr)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sess)
|
||||
}
|
||||
|
||||
func TestCreateMusig2Sessions_Multiple(t *testing.T) {
|
||||
lnd := looptest.NewMockLnd()
|
||||
signer := lnd.Signer
|
||||
|
||||
// Keys/params/static address.
|
||||
clientKey, err := btcec.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
serverKey, err := btcec.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
params := &address.Parameters{
|
||||
ClientPubkey: clientKey.PubKey(),
|
||||
ServerPubkey: serverKey.PubKey(),
|
||||
Expiry: 12,
|
||||
PkScript: []byte{0xaa},
|
||||
KeyLocator: keychain.KeyLocator{Family: 9, Index: 8},
|
||||
}
|
||||
|
||||
staticAddr, err := script.NewStaticAddress(
|
||||
input.MuSig2Version100RC2, int64(params.Expiry), params.ClientPubkey, params.ServerPubkey,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Prepare N deposits; only the length matters for session count.
|
||||
deposits := []*deposit.Deposit{
|
||||
{OutPoint: wire.OutPoint{Index: 0}},
|
||||
{OutPoint: wire.OutPoint{Index: 1}},
|
||||
{OutPoint: wire.OutPoint{Index: 2}},
|
||||
}
|
||||
|
||||
sessions, nonces, err := CreateMusig2Sessions(
|
||||
context.Background(), signer, deposits, params, staticAddr,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, sessions, len(deposits))
|
||||
require.Len(t, nonces, len(deposits))
|
||||
|
||||
// The mock signer returns a zero-value PublicNonce; assert consistency.
|
||||
for i := range sessions {
|
||||
require.NotNil(t, sessions[i])
|
||||
require.True(t, bytes.Equal(nonces[i], sessions[i].PublicNonce[:]))
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue