|
| 1 | +package staticutil |
| 2 | + |
| 3 | +import ( |
| 4 | + "bytes" |
| 5 | + "context" |
| 6 | + "fmt" |
| 7 | + "sort" |
| 8 | + |
| 9 | + "github.com/btcsuite/btcd/chaincfg/chainhash" |
| 10 | + "github.com/btcsuite/btcd/wire" |
| 11 | + "github.com/lightninglabs/lndclient" |
| 12 | + "github.com/lightninglabs/loop/staticaddr/address" |
| 13 | + "github.com/lightninglabs/loop/staticaddr/deposit" |
| 14 | + "github.com/lightninglabs/loop/staticaddr/script" |
| 15 | + "github.com/lightninglabs/loop/swapserverrpc" |
| 16 | + "github.com/lightningnetwork/lnd/input" |
| 17 | +) |
| 18 | + |
| 19 | +func ToPrevOuts(deposits []*deposit.Deposit, |
| 20 | + pkScript []byte) (map[wire.OutPoint]*wire.TxOut, error) { |
| 21 | + |
| 22 | + prevOuts := make(map[wire.OutPoint]*wire.TxOut, len(deposits)) |
| 23 | + for _, d := range deposits { |
| 24 | + outpoint := wire.OutPoint{ |
| 25 | + Hash: d.Hash, |
| 26 | + Index: d.Index, |
| 27 | + } |
| 28 | + txOut := &wire.TxOut{ |
| 29 | + Value: int64(d.Value), |
| 30 | + PkScript: pkScript, |
| 31 | + } |
| 32 | + if _, ok := prevOuts[outpoint]; ok { |
| 33 | + return nil, fmt.Errorf("duplicate outpoint %v", |
| 34 | + outpoint) |
| 35 | + } |
| 36 | + prevOuts[outpoint] = txOut |
| 37 | + } |
| 38 | + |
| 39 | + return prevOuts, nil |
| 40 | +} |
| 41 | + |
| 42 | +// CreateMusig2Sessions creates a musig2 session for a number of deposits. |
| 43 | +func CreateMusig2Sessions(ctx context.Context, |
| 44 | + signer lndclient.SignerClient, deposits []*deposit.Deposit, |
| 45 | + addrParams *address.Parameters, |
| 46 | + staticAddress *script.StaticAddress) ([]*input.MuSig2SessionInfo, |
| 47 | + [][]byte, error) { |
| 48 | + |
| 49 | + musig2Sessions := make([]*input.MuSig2SessionInfo, len(deposits)) |
| 50 | + clientNonces := make([][]byte, len(deposits)) |
| 51 | + |
| 52 | + // Create the sessions and nonces from the deposits. |
| 53 | + for i := 0; i < len(deposits); i++ { |
| 54 | + session, err := createMusig2Session( |
| 55 | + ctx, signer, addrParams, staticAddress, |
| 56 | + ) |
| 57 | + if err != nil { |
| 58 | + return nil, nil, err |
| 59 | + } |
| 60 | + |
| 61 | + musig2Sessions[i] = session |
| 62 | + clientNonces[i] = session.PublicNonce[:] |
| 63 | + } |
| 64 | + |
| 65 | + return musig2Sessions, clientNonces, nil |
| 66 | +} |
| 67 | + |
| 68 | +// createMusig2Session creates a musig2 session for the deposit. |
| 69 | +func createMusig2Session(ctx context.Context, |
| 70 | + signer lndclient.SignerClient, addrParams *address.Parameters, |
| 71 | + staticAddress *script.StaticAddress) (*input.MuSig2SessionInfo, error) { |
| 72 | + |
| 73 | + signers := [][]byte{ |
| 74 | + addrParams.ClientPubkey.SerializeCompressed(), |
| 75 | + addrParams.ServerPubkey.SerializeCompressed(), |
| 76 | + } |
| 77 | + |
| 78 | + expiryLeaf := staticAddress.TimeoutLeaf |
| 79 | + |
| 80 | + rootHash := expiryLeaf.TapHash() |
| 81 | + |
| 82 | + return signer.MuSig2CreateSession( |
| 83 | + ctx, input.MuSig2Version100RC2, &addrParams.KeyLocator, |
| 84 | + signers, lndclient.MuSig2TaprootTweakOpt(rootHash[:], false), |
| 85 | + ) |
| 86 | +} |
| 87 | + |
| 88 | +// GetPrevoutInfo converts a map of prevOuts to protobuf. |
| 89 | +func GetPrevoutInfo(prevOuts map[wire.OutPoint]*wire.TxOut, |
| 90 | +) []*swapserverrpc.PrevoutInfo { |
| 91 | + |
| 92 | + prevoutInfos := make([]*swapserverrpc.PrevoutInfo, 0, len(prevOuts)) |
| 93 | + |
| 94 | + for outpoint, txOut := range prevOuts { |
| 95 | + prevoutInfo := &swapserverrpc.PrevoutInfo{ |
| 96 | + TxidBytes: outpoint.Hash[:], |
| 97 | + OutputIndex: outpoint.Index, |
| 98 | + Value: uint64(txOut.Value), |
| 99 | + PkScript: txOut.PkScript, |
| 100 | + } |
| 101 | + prevoutInfos = append(prevoutInfos, prevoutInfo) |
| 102 | + } |
| 103 | + |
| 104 | + // Sort UTXOs by txid:index using BIP-0069 rule. The function is used |
| 105 | + // in unit tests a lot, and it is useful to make it deterministic. |
| 106 | + sort.Slice(prevoutInfos, func(i, j int) bool { |
| 107 | + return bip69inputLess(prevoutInfos[i], prevoutInfos[j]) |
| 108 | + }) |
| 109 | + |
| 110 | + return prevoutInfos |
| 111 | +} |
| 112 | + |
| 113 | +// bip69inputLess returns true if input1 < input2 according to BIP-0069 |
| 114 | +// First sort based on input hash (reversed / rpc-style), then index. |
| 115 | +// The code is based on btcd/btcutil/txsort/txsort.go. |
| 116 | +func bip69inputLess(input1, input2 *swapserverrpc.PrevoutInfo) bool { |
| 117 | + // Input hashes are the same, so compare the index. |
| 118 | + var ihash, jhash chainhash.Hash |
| 119 | + copy(ihash[:], input1.TxidBytes) |
| 120 | + copy(jhash[:], input2.TxidBytes) |
| 121 | + if ihash == jhash { |
| 122 | + return input1.OutputIndex < input2.OutputIndex |
| 123 | + } |
| 124 | + |
| 125 | + // At this point, the hashes are not equal, so reverse them to |
| 126 | + // big-endian and return the result of the comparison. |
| 127 | + const hashSize = chainhash.HashSize |
| 128 | + for b := 0; b < hashSize/2; b++ { |
| 129 | + ihash[b], ihash[hashSize-1-b] = ihash[hashSize-1-b], ihash[b] |
| 130 | + jhash[b], jhash[hashSize-1-b] = jhash[hashSize-1-b], jhash[b] |
| 131 | + } |
| 132 | + return bytes.Compare(ihash[:], jhash[:]) == -1 |
| 133 | +} |
0 commit comments