diff --git a/client_test.go b/client_test.go index 6a315bc..3de278b 100644 --- a/client_test.go +++ b/client_test.go @@ -216,12 +216,10 @@ func testResume(t *testing.T, confs uint32, expired, preimageRevealed, } _, senderPubKey := test.CreateKey(1) - var senderKey [33]byte - copy(senderKey[:], senderPubKey.SerializeCompressed()) + senderKey := senderPubKey.SerializeCompressed() _, receiverPubKey := test.CreateKey(2) - var receiverKey [33]byte - copy(receiverKey[:], receiverPubKey.SerializeCompressed()) + receiverKey := receiverPubKey.SerializeCompressed() update := loopdb.LoopEvent{ SwapStateData: loopdb.SwapStateData{ diff --git a/loopd/swapclient_server.go b/loopd/swapclient_server.go index db80475..c1dcd22 100644 --- a/loopd/swapclient_server.go +++ b/loopd/swapclient_server.go @@ -211,26 +211,54 @@ func (s *swapClientServer) marshallSwap(loopSwap *loop.SwapInfo) ( if failureReason != clientrpc.FailureReason_FAILURE_REASON_NONE { state = clientrpc.SwapState_FAILED } - + scriptVer := loop.GetHtlcScriptVersion(loopSwap.ProtocolVersion) var swapType clientrpc.SwapType - var htlcAddress, htlcAddressP2WSH, htlcAddressNP2WSH string - + var htlcAddress, htlcAddressP2WSH, htlcAddressP2TR, htlcAddressNP2WSH string switch loopSwap.SwapType { case swap.TypeIn: swapType = clientrpc.SwapType_LOOP_IN htlcAddressP2WSH = loopSwap.HtlcAddressP2WSH.EncodeAddress() + // Determine which address to set depending on script version. + // If the loop in is external, we'll default to v1 if loopSwap.ExternalHtlc { htlcAddressNP2WSH = loopSwap.HtlcAddressNP2WSH.EncodeAddress() htlcAddress = htlcAddressNP2WSH - } else { + } + + switch scriptVer { + case swap.HtlcV1: + htlcAddressNP2WSH = loopSwap.HtlcAddressNP2WSH.EncodeAddress() + htlcAddress = htlcAddressNP2WSH + + case swap.HtlcV2: + htlcAddressP2WSH = loopSwap.HtlcAddressP2WSH.EncodeAddress() htlcAddress = htlcAddressP2WSH + + case swap.HtlcV3: + htlcAddressP2TR = loopSwap.HtlcAddressP2TR.EncodeAddress() + htlcAddress = htlcAddressP2TR + + default: + return nil, errors.New("unknown script version") } case swap.TypeOut: swapType = clientrpc.SwapType_LOOP_OUT - htlcAddressP2WSH = loopSwap.HtlcAddressP2WSH.EncodeAddress() - htlcAddress = htlcAddressP2WSH + + switch scriptVer { + case swap.HtlcV2: + htlcAddressP2WSH = loopSwap.HtlcAddressP2WSH.EncodeAddress() + htlcAddress = htlcAddressP2WSH + + case swap.HtlcV3: + htlcAddressP2TR = loopSwap.HtlcAddressP2TR.EncodeAddress() + htlcAddress = htlcAddressP2TR + + default: + return nil, errors.New("unknown script version") + + } default: return nil, errors.New("unknown swap type") @@ -245,6 +273,7 @@ func (s *swapClientServer) marshallSwap(loopSwap *loop.SwapInfo) ( InitiationTime: loopSwap.InitiationTime.UnixNano(), LastUpdateTime: loopSwap.LastUpdate.UnixNano(), HtlcAddress: htlcAddress, + HtlcAddressP2Tr: htlcAddressP2TR, HtlcAddressP2Wsh: htlcAddressP2WSH, HtlcAddressNp2Wsh: htlcAddressNP2WSH, Type: swapType, diff --git a/loopdb/protocol_version_test.go b/loopdb/protocol_version_test.go index bcaa913..17438ce 100644 --- a/loopdb/protocol_version_test.go +++ b/loopdb/protocol_version_test.go @@ -24,6 +24,7 @@ func TestProtocolVersionSanity(t *testing.T) { ProtocolVersionLoopOutCancel, ProtocolVersionProbe, ProtocolVersionRoutingPlugin, + ProtocolVersionTaproot, } rpcVersions := [...]looprpc.ProtocolVersion{ @@ -37,6 +38,7 @@ func TestProtocolVersionSanity(t *testing.T) { looprpc.ProtocolVersion_LOOP_OUT_CANCEL, looprpc.ProtocolVersion_PROBE, looprpc.ProtocolVersion_ROUTING_PLUGIN, + looprpc.ProtocolVersion_TAPROOT, } require.Equal(t, len(versions), len(rpcVersions)) diff --git a/loopin.go b/loopin.go index 06328a9..d88a94d 100644 --- a/loopin.go +++ b/loopin.go @@ -58,6 +58,8 @@ type loopInSwap struct { htlc *swap.Htlc + htlcP2TR *swap.Htlc + htlcP2WSH *swap.Htlc htlcNP2WSH *swap.Htlc @@ -405,6 +407,11 @@ func validateLoopInContract(lnd *lndclient.LndServices, // initHtlcs creates and updates the native and nested segwit htlcs // of the loopInSwap. func (s *loopInSwap) initHtlcs() error { + htlcP2TR, err := s.swapKit.getHtlc(swap.HtlcP2TR) + if err != nil { + return err + } + htlcP2WSH, err := s.swapKit.getHtlc(swap.HtlcP2WSH) if err != nil { return err @@ -418,7 +425,9 @@ func (s *loopInSwap) initHtlcs() error { // Log htlc addresses for debugging. s.swapKit.log.Infof("Htlc address (P2WSH): %v", htlcP2WSH.Address) s.swapKit.log.Infof("Htlc address (NP2WSH): %v", htlcNP2WSH.Address) + s.swapKit.log.Infof("P2TR address (NP2WSH): %v", htlcP2TR.Address) + s.htlcP2TR = htlcP2TR s.htlcP2WSH = htlcP2WSH s.htlcNP2WSH = htlcNP2WSH @@ -432,6 +441,7 @@ func (s *loopInSwap) sendUpdate(ctx context.Context) error { info.HtlcAddressP2WSH = s.htlcP2WSH.Address info.HtlcAddressNP2WSH = s.htlcNP2WSH.Address + info.HtlcAddressP2TR = s.htlcP2TR.Address info.ExternalHtlc = s.ExternalHtlc select { @@ -600,15 +610,8 @@ func (s *loopInSwap) waitForHtlcConf(globalCtx context.Context) ( notifier := s.lnd.ChainNotifier - confChanP2WSH, confErrP2WSH, err := notifier.RegisterConfirmationsNtfn( - ctx, s.htlcTxHash, s.htlcP2WSH.PkScript, 1, s.InitiationHeight, - ) - if err != nil { - return nil, err - } - - confChanNP2WSH, confErrNP2WSH, err := notifier.RegisterConfirmationsNtfn( - ctx, s.htlcTxHash, s.htlcNP2WSH.PkScript, 1, s.InitiationHeight, + confChanP2TR, confErrP2TR, err := notifier.RegisterConfirmationsNtfn( + ctx, s.htlcTxHash, s.htlcP2TR.PkScript, 1, s.InitiationHeight, ) if err != nil { return nil, err @@ -618,22 +621,13 @@ func (s *loopInSwap) waitForHtlcConf(globalCtx context.Context) ( for conf == nil { select { - // P2WSH htlc confirmed. - case conf = <-confChanP2WSH: - s.htlc = s.htlcP2WSH - s.log.Infof("P2WSH htlc confirmed") - - // NP2WSH htlc confirmed. - case conf = <-confChanNP2WSH: - s.htlc = s.htlcNP2WSH - s.log.Infof("NP2WSH htlc confirmed") - - // Conf ntfn error. - case err := <-confErrP2WSH: - return nil, err + // P2TR htlc confirmed. + case conf = <-confChanP2TR: + s.htlc = s.htlcP2TR + s.log.Infof("P2TR htlc confirmed") // Conf ntfn error. - case err := <-confErrNP2WSH: + case err = <-confErrP2TR: return nil, err // Keep up with block height. @@ -692,10 +686,10 @@ func (s *loopInSwap) publishOnChainHtlc(ctx context.Context) (bool, error) { s.log.Infof("Publishing on chain HTLC with fee rate %v", feeRate) - // Internal loop-in is always P2WSH. + // Internal loop-in is always P2TR. tx, err := s.lnd.WalletKit.SendOutputs( ctx, []*wire.TxOut{{ - PkScript: s.htlcP2WSH.PkScript, + PkScript: s.htlcP2TR.PkScript, Value: int64(s.LoopInContract.AmountRequested), }}, feeRate, labels.LoopInHtlcLabel(swap.ShortHash(&s.hash)), ) @@ -944,10 +938,20 @@ func (s *loopInSwap) publishTimeoutTx(ctx context.Context, return s.htlc.GenTimeoutWitness(sig), nil } + // TODO(arshbot): replace with a more holistic Script func + var witnessScript []byte + var trHtlc *swap.HtlcScriptV3 + trHtlc, ok := s.htlc.HtlcScript.(*swap.HtlcScriptV3) + if !ok { + witnessScript = s.htlc.Script() + } else { + witnessScript = trHtlc.TimeoutScript + } + sequence := uint32(0) timeoutTx, err := s.sweeper.CreateSweepTx( ctx, s.height, sequence, s.htlc, *htlcOutpoint, s.SenderKey, - witnessFunc, htlcValue, fee, s.timeoutAddr, + witnessScript, witnessFunc, htlcValue, fee, s.timeoutAddr, ) if err != nil { return 0, err diff --git a/loopin_test.go b/loopin_test.go index a5ded02..ff02027 100644 --- a/loopin_test.go +++ b/loopin_test.go @@ -345,8 +345,8 @@ func testLoopInResume(t *testing.T, state loopdb.SwapState, expired bool, ctx := newLoopInTestContext(t) cfg := newSwapConfig(&ctx.lnd.LndServices, ctx.store, ctx.server) - senderKey := [33]byte{4} - receiverKey := [33]byte{5} + senderKey := []byte{4} + receiverKey := []byte{5} contract := &loopdb.LoopInContract{ HtlcConfTarget: 2, diff --git a/loopout.go b/loopout.go index 2e7921d..5c4c263 100644 --- a/loopout.go +++ b/loopout.go @@ -304,7 +304,7 @@ func (s *loopOutSwap) sendUpdate(ctx context.Context) error { info := s.swapInfo() s.log.Infof("Loop out swap state: %v", info.State) - info.HtlcAddressP2WSH = s.htlc.Address + info.HtlcAddressP2TR = s.htlc.Address select { case s.statusChan <- *info: @@ -488,6 +488,7 @@ func (s *loopOutSwap) executeSwap(globalCtx context.Context) error { htlcOutpoint, htlcValue, err := swap.GetScriptOutput( txConf.Tx, s.htlc.PkScript, ) + if err != nil { return err } @@ -857,6 +858,7 @@ func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) ( ctx, cancel := context.WithCancel(globalCtx) defer cancel() + htlcConfChan, htlcErrChan, err := s.lnd.ChainNotifier.RegisterConfirmationsNtfn( ctx, s.htlcTxHash, s.htlc.PkScript, @@ -950,6 +952,7 @@ func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) ( // Unexpected error on the confirm channel happened, // abandon the swap. case err := <-htlcErrChan: + s.log.Infof(err.Error()) return nil, err // Htlc got confirmed, continue to sweeping. @@ -1284,10 +1287,20 @@ func (s *loopOutSwap) sweep(ctx context.Context, } } + // TODO(arshbot): replace with a more holistic Script func + var witnessScript []byte + var trHtlc *swap.HtlcScriptV3 + trHtlc, ok := s.htlc.HtlcScript.(*swap.HtlcScriptV3) + if !ok { + witnessScript = s.htlc.Script() + } else { + witnessScript = trHtlc.ClaimScript + } + // Create sweep tx. sweepTx, err := s.sweeper.CreateSweepTx( ctx, s.height, s.htlc.SuccessSequence(), s.htlc, htlcOutpoint, - s.ReceiverKey, witnessFunc, htlcValue, fee, s.DestAddr, + s.ReceiverKey, witnessScript, witnessFunc, htlcValue, fee, s.DestAddr, ) if err != nil { return err diff --git a/server_mock_test.go b/server_mock_test.go index 2f12f2e..1caa45f 100644 --- a/server_mock_test.go +++ b/server_mock_test.go @@ -71,7 +71,7 @@ func newServerMock(lnd *test.LndMockServices) *serverMock { } func (s *serverMock) NewLoopOutSwap(_ context.Context, swapHash lntypes.Hash, - amount btcutil.Amount, _ int32, _ [33]byte, _ time.Time, + amount btcutil.Amount, _ int32, _ []byte, _ time.Time, _ string) (*newLoopOutResponse, error) { _, senderKey := test.CreateKey(100) @@ -92,11 +92,8 @@ func (s *serverMock) NewLoopOutSwap(_ context.Context, swapHash lntypes.Hash, return nil, err } - var senderKeyArray [33]byte - copy(senderKeyArray[:], senderKey.SerializeCompressed()) - return &newLoopOutResponse{ - senderKey: senderKeyArray, + senderKey: senderKey.SerializeCompressed(), swapInvoice: swapPayReqString, prepayInvoice: prePayReqString, }, nil diff --git a/swap/htlc.go b/swap/htlc.go index b55bd99..4128a32 100644 --- a/swap/htlc.go +++ b/swap/htlc.go @@ -215,7 +215,7 @@ func NewHtlc(version ScriptVersion, cltvExpiry int32, // Generate a tapscript address from our tree address, err = btcutil.NewAddressTaproot( - schnorr.SerializePubKey(trHtlc.taprootKey), chainParams, + schnorr.SerializePubKey(trHtlc.TaprootKey), chainParams, ) if err != nil { return nil, err @@ -239,24 +239,6 @@ func NewHtlc(version ScriptVersion, cltvExpiry int32, Address: address, SigScript: sigScript, }, nil - - /* - { - HtlcScript: - timeoutScript []byte - claimScript []byte - internalPubKey *secp.PublicKey - senderKey [33]byte - }, - Hash: preimage, - Version: HtlcV3, - PkScript: p2trPkScript or nil - OutputType: HtlcP2WSH, - ChainParams: chainParams, - Address: tapScriptAddr, - SigScript: nil, - } - */ } // GenSuccessWitness returns the success script to spend this htlc with @@ -564,11 +546,11 @@ func (h *HtlcScriptV2) SuccessSequence() uint32 { // HtlcScriptV2 encapsulates the htlc v2 script. type HtlcScriptV3 struct { - timeoutScript []byte - claimScript []byte - taprootKey *secp.PublicKey - internalPubKey *secp.PublicKey - senderKey []byte + TimeoutScript []byte + ClaimScript []byte + TaprootKey *secp.PublicKey + InternalPubKey *secp.PublicKey + SenderKey []byte } func newHTLCScriptV3( @@ -634,18 +616,18 @@ func newHTLCScriptV3( ) return &HtlcScriptV3{ - timeoutScript: timeoutPathScript, - claimScript: claimPathScript, - taprootKey: taprootKey, - internalPubKey: internalPubKey, - senderKey: senderHtlcKey, + TimeoutScript: timeoutPathScript, + ClaimScript: claimPathScript, + TaprootKey: taprootKey, + InternalPubKey: internalPubKey, + SenderKey: senderHtlcKey, }, nil } func (h *HtlcScriptV3) genControlBlock(leafScript []byte) ([]byte, error) { var outputKeyYIsOdd bool - if h.taprootKey.SerializeCompressed()[0] == secp.PubKeyFormatCompressedOdd { + if h.TaprootKey.SerializeCompressed()[0] == secp.PubKeyFormatCompressedOdd { outputKeyYIsOdd = true } @@ -653,7 +635,7 @@ func (h *HtlcScriptV3) genControlBlock(leafScript []byte) ([]byte, error) { proof := leaf.TapHash() controlBlock := txscript.ControlBlock{ - InternalKey: h.internalPubKey, + InternalKey: h.InternalPubKey, OutputKeyYIsOdd: outputKeyYIsOdd, LeafVersion: txscript.BaseLeafVersion, InclusionProof: proof[:], @@ -668,23 +650,23 @@ func (h *HtlcScriptV3) genControlBlock(leafScript []byte) ([]byte, error) { func (h *HtlcScriptV3) genSuccessWitness(signature []byte, preimage lntypes.Preimage) wire.TxWitness { - // TODO: Unsilence errors - controlBlockBytes, _ := h.genControlBlock(h.timeoutScript) + // TODO(arshbot): Unsilence errors + controlBlockBytes, _ := h.genControlBlock(h.TimeoutScript) return wire.TxWitness{ preimage[:], signature, - h.claimScript, + h.ClaimScript, controlBlockBytes, } } func (h *HtlcScriptV3) GenTimeoutWitness(senderSig []byte) wire.TxWitness { - // TODO: Unsilence errors - controlBlockBytes, _ := h.genControlBlock(h.claimScript) + // TODO(arshbot): Unsilence errors + controlBlockBytes, _ := h.genControlBlock(h.ClaimScript) return wire.TxWitness{ senderSig, - h.timeoutScript, + h.TimeoutScript, controlBlockBytes, } } @@ -706,5 +688,5 @@ func (h *HtlcScriptV3) MaxTimeoutWitnessSize() int { } func (h *HtlcScriptV3) SuccessSequence() uint32 { - return 10 + return 1 } diff --git a/swap/htlc_test.go b/swap/htlc_test.go index f7a9525..e069eee 100644 --- a/swap/htlc_test.go +++ b/swap/htlc_test.go @@ -398,7 +398,7 @@ func TestHtlcV3(t *testing.T) { require.True(t, ok) sig := signTx( - tx, senderPrivKey, txscript.NewBaseTapLeaf(trHtlc.claimScript), + tx, senderPrivKey, txscript.NewBaseTapLeaf(trHtlc.ClaimScript), ) return htlc.genSuccessWitness(sig, preimage) @@ -414,7 +414,7 @@ func TestHtlcV3(t *testing.T) { require.True(t, ok) sig := signTx( - tx, receiverPrivKey, txscript.NewBaseTapLeaf(trHtlc.timeoutScript), + tx, receiverPrivKey, txscript.NewBaseTapLeaf(trHtlc.TimeoutScript), ) return htlc.GenTimeoutWitness(sig) diff --git a/sweep/sweeper.go b/sweep/sweeper.go index 71f21f3..c16b2df 100644 --- a/sweep/sweeper.go +++ b/sweep/sweeper.go @@ -23,7 +23,7 @@ type Sweeper struct { func (s *Sweeper) CreateSweepTx( globalCtx context.Context, height int32, sequence uint32, htlc *swap.Htlc, htlcOutpoint wire.OutPoint, - keyBytes []byte, + keyBytes, witnessScript []byte, witnessFunc func(sig []byte) (wire.TxWitness, error), amount, fee btcutil.Amount, destAddr btcutil.Address) (*wire.MsgTx, error) { @@ -52,18 +52,17 @@ func (s *Sweeper) CreateSweepTx( }) // Generate a signature for the swap htlc transaction. - key, err := btcec.ParsePubKey(keyBytes) if err != nil { return nil, err } signDesc := lndclient.SignDescriptor{ - WitnessScript: htlc.Script(), + WitnessScript: witnessScript, Output: &wire.TxOut{ Value: int64(amount), }, - HashType: txscript.SigHashAll, + HashType: txscript.SigHashDefault, InputIndex: 0, KeyDesc: keychain.KeyDescriptor{ PubKey: key,