@@ -9,18 +9,24 @@ import (
99
1010 "github.com/btcsuite/btcd/btcec/v2"
1111 "github.com/btcsuite/btcd/btcec/v2/schnorr"
12+ "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
1213 "github.com/btcsuite/btcd/btcutil/psbt"
1314 "github.com/btcsuite/btcd/txscript"
1415 "github.com/btcsuite/btcd/wire"
1516 "github.com/btcsuite/btcwallet/wallet"
1617 "github.com/btcsuite/btcwallet/wtxmgr"
1718 "github.com/lightninglabs/lndclient"
1819 "github.com/lightninglabs/loop/assets"
20+ "github.com/lightninglabs/loop/assets/htlc"
1921 "github.com/lightninglabs/loop/utils"
2022 "github.com/lightninglabs/taproot-assets/address"
2123 "github.com/lightninglabs/taproot-assets/proof"
24+ "github.com/lightninglabs/taproot-assets/tappsbt"
2225 "github.com/lightninglabs/taproot-assets/taprpc"
26+ "github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc"
2327 "github.com/lightningnetwork/lnd/input"
28+ "github.com/lightningnetwork/lnd/keychain"
29+ "github.com/lightningnetwork/lnd/lntypes"
2430 "github.com/lightningnetwork/lnd/lnwallet/chainfee"
2531)
2632
@@ -333,3 +339,110 @@ func getSigHash(tx *wire.MsgTx, idx int,
333339
334340 return sigHash , nil
335341}
342+
343+ // GetHTLC creates a new zero-fee HTLC packet to be able to partially sign it
344+ // and send it to the server for further processing.
345+ //
346+ // TODO(bhandras): add support for spending multiple deposits into the HTLC.
347+ func (s * Sweeper ) GetHTLC (ctx context.Context , deposit * Kit ,
348+ depositProof * proof.Proof , amount uint64 , hash lntypes.Hash ,
349+ csvExpiry uint32 ) (* htlc.SwapKit , * psbt.Packet , []* tappsbt.VPacket ,
350+ []* tappsbt.VPacket , * assetwalletrpc.CommitVirtualPsbtsResponse , error ) {
351+
352+ // Genearate the HTLC address that will be used to sweep the deposit to
353+ // in case the client is uncooperative.
354+ rpcHtlcAddr , swapKit , err := deposit .NewHtlcAddr (
355+ ctx , s .tapdClient , amount , hash , csvExpiry ,
356+ )
357+ if err != nil {
358+ return nil , nil , nil , nil , nil , fmt .Errorf ("unable to create " +
359+ "htlc addr: %v" , err )
360+ }
361+
362+ htlcAddr , err := address .DecodeAddress (
363+ rpcHtlcAddr .Encoded , & s .addressParams ,
364+ )
365+ if err != nil {
366+ return nil , nil , nil , nil , nil , err
367+ }
368+
369+ // Now we can create the sweep vpacket that'd sweep the deposited
370+ // assets to the HTLC output.
371+ depositSpendVpkt , err := assets .CreateOpTrueSweepVpkt (
372+ ctx , []* proof.Proof {depositProof }, htlcAddr , & s .addressParams ,
373+ )
374+ if err != nil {
375+ return nil , nil , nil , nil , nil , fmt .Errorf ("unable to create " +
376+ "deposit spend vpacket: %v" , err )
377+ }
378+
379+ // By committing the virtual transaction to the BTC template we
380+ // created, our lnd node will fund the BTC level transaction with an
381+ // input to pay for the fees. We'll further add a change output to the
382+ // transaction that will be generated using the above key descriptor.
383+ feeRate := chainfee .SatPerVByte (0 )
384+
385+ // Use an empty lock ID, as we don't need to lock any UTXOs for this
386+ // operation.
387+ lockID := wtxmgr.LockID {}
388+
389+ htlcBtcPkt , activeAssets , passiveAssets , commitResp , err :=
390+ s .tapdClient .PrepareAndCommitVirtualPsbts (
391+ ctx , depositSpendVpkt , feeRate , nil ,
392+ s .addressParams .Params , nil , & lockID ,
393+ time .Duration (0 ),
394+ )
395+ if err != nil {
396+ return nil , nil , nil , nil , nil , fmt .Errorf ("deposit spend " +
397+ "HTLC prepare and commit failed: %v" , err )
398+ }
399+
400+ htlcBtcPkt .UnsignedTx .Version = 3
401+
402+ return swapKit , htlcBtcPkt , activeAssets , passiveAssets , commitResp , nil
403+ }
404+
405+ // PartialSignMuSig2 is used to partially sign a message hash with the deposit's
406+ // keys.
407+ func (s * Sweeper ) PartialSignMuSig2 (ctx context.Context , d * Kit ,
408+ anchorRootHash []byte , cosignerNonce [musig2 .PubNonceSize ]byte ,
409+ message [32 ]byte ) ([musig2 .PubNonceSize ]byte , []byte , error ) {
410+
411+ signers := [][]byte {
412+ d .FunderInternalKey .SerializeCompressed (),
413+ d .CoSignerInternalKey .SerializeCompressed (),
414+ }
415+
416+ session , err := s .signer .MuSig2CreateSession (
417+ ctx , input .MuSig2Version100RC2 ,
418+ & keychain.KeyLocator {
419+ Family : d .KeyLocator .Family ,
420+ Index : d .KeyLocator .Index ,
421+ }, signers , lndclient .MuSig2TaprootTweakOpt (
422+ anchorRootHash , false ,
423+ ),
424+ )
425+ if err != nil {
426+ return [musig2 .PubNonceSize ]byte {}, nil , err
427+ }
428+
429+ _ , err = s .signer .MuSig2RegisterNonces (
430+ ctx , session .SessionID ,
431+ [][musig2 .PubNonceSize ]byte {cosignerNonce },
432+ )
433+ if err != nil {
434+ return [musig2 .PubNonceSize ]byte {}, nil , err
435+ }
436+
437+ clientPartialSig , err := s .signer .MuSig2Sign (
438+ ctx , session .SessionID , message , true ,
439+ )
440+ if err != nil {
441+ return [musig2 .PubNonceSize ]byte {}, nil , err
442+ }
443+
444+ fmt .Printf ("!!! client partial sig: %x\n " , clientPartialSig )
445+ fmt .Printf ("!!! client nonce: %x\n " , session .PublicNonce )
446+
447+ return session .PublicNonce , clientPartialSig , nil
448+ }
0 commit comments