diff --git a/assets/client.go b/assets/client.go index 88c23efe5..95052c7bc 100644 --- a/assets/client.go +++ b/assets/client.go @@ -1,6 +1,7 @@ package assets import ( + "bytes" "context" "encoding/hex" "fmt" @@ -9,19 +10,38 @@ import ( "time" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/wtxmgr" + "github.com/lightninglabs/lndclient" + tap "github.com/lightninglabs/taproot-assets" + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/proof" "github.com/lightninglabs/taproot-assets/rfqmath" "github.com/lightninglabs/taproot-assets/rpcutils" "github.com/lightninglabs/taproot-assets/tapcfg" + "github.com/lightninglabs/taproot-assets/tapfreighter" + "github.com/lightninglabs/taproot-assets/tappsbt" "github.com/lightninglabs/taproot-assets/taprpc" + "github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc" "github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc" "github.com/lightninglabs/taproot-assets/taprpc/rfqrpc" "github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc" "github.com/lightninglabs/taproot-assets/taprpc/universerpc" + "github.com/lightninglabs/taproot-assets/tapsend" + "github.com/lightninglabs/taproot-assets/universe" + "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnwallet/btcwallet" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/macaroons" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" + "google.golang.org/grpc/status" "gopkg.in/macaroon.v2" ) @@ -29,7 +49,7 @@ var ( // maxMsgRecvSize is the largest message our client will receive. We // set this to 200MiB atm. - maxMsgRecvSize = grpc.MaxCallRecvMsgSize(1 * 1024 * 1024 * 200) + maxMsgRecvSize = grpc.MaxCallRecvMsgSize(200 * 1024 * 1024) // defaultRfqTimeout is the default timeout we wait for tapd peer to // accept RFQ. @@ -66,6 +86,7 @@ type TapdClient struct { priceoraclerpc.PriceOracleClient rfqrpc.RfqClient universerpc.UniverseClient + assetwalletrpc.AssetWalletClient cfg *TapdConfig assetNameCache map[string]string @@ -73,6 +94,43 @@ type TapdClient struct { cc *grpc.ClientConn } +func getClientConn(config *TapdConfig) (*grpc.ClientConn, error) { + // Load the specified TLS certificate and build transport credentials. + creds, err := credentials.NewClientTLSFromFile(config.TLSPath, "") + if err != nil { + return nil, err + } + + // Load the specified macaroon file. + macBytes, err := os.ReadFile(config.MacaroonPath) + if err != nil { + return nil, err + } + mac := &macaroon.Macaroon{} + if err := mac.UnmarshalBinary(macBytes); err != nil { + return nil, err + } + + macaroon, err := macaroons.NewMacaroonCredential(mac) + if err != nil { + return nil, err + } + // Create the DialOptions with the macaroon credentials. + opts := []grpc.DialOption{ + grpc.WithTransportCredentials(creds), + grpc.WithPerRPCCredentials(macaroon), + grpc.WithDefaultCallOptions(maxMsgRecvSize), + } + + // Dial the gRPC server. + conn, err := grpc.Dial(config.Host, opts...) + if err != nil { + return nil, err + } + + return conn, nil +} + // NewTapdClient returns a new taproot assets client. func NewTapdClient(config *TapdConfig) (*TapdClient, error) { // Create the client connection to the server. @@ -91,6 +149,7 @@ func NewTapdClient(config *TapdConfig) (*TapdClient, error) { PriceOracleClient: priceoraclerpc.NewPriceOracleClient(conn), RfqClient: rfqrpc.NewRfqClient(conn), UniverseClient: universerpc.NewUniverseClient(conn), + AssetWalletClient: assetwalletrpc.NewAssetWalletClient(conn), } return client, nil @@ -220,13 +279,15 @@ func (c *TapdClient) GetAssetPrice(ctx context.Context, assetID string, } if rfq.GetInvalidQuote() != nil { - return 0, fmt.Errorf("peer %v sent an invalid quote response %v for "+ - "asset %v", peerPubkey, rfq.GetInvalidQuote(), assetID) + return 0, fmt.Errorf("peer %v sent an invalid quote response "+ + "%v for asset %v", peerPubkey, rfq.GetInvalidQuote(), + assetID) } if rfq.GetRejectedQuote() != nil { return 0, fmt.Errorf("peer %v rejected the quote request for "+ - "asset %v, %v", peerPubkey, assetID, rfq.GetRejectedQuote()) + "asset %v, %v", peerPubkey, assetID, + rfq.GetRejectedQuote()) } acceptedRes := rfq.GetAcceptedQuote() @@ -255,6 +316,435 @@ func getSatsFromAssetAmt(assetAmt uint64, assetRate *rfqrpc.FixedPoint) ( return msatAmt.ToSatoshis(), nil } +// FundAndSignVpacket funds and signs a vpacket. +func (t *TapdClient) FundAndSignVpacket(ctx context.Context, + vpkt *tappsbt.VPacket) (*tappsbt.VPacket, error) { + + // Fund the packet. + var buf bytes.Buffer + err := vpkt.Serialize(&buf) + if err != nil { + return nil, err + } + + fundResp, err := t.FundVirtualPsbt( + ctx, &assetwalletrpc.FundVirtualPsbtRequest{ + Template: &assetwalletrpc.FundVirtualPsbtRequest_Psbt{ + Psbt: buf.Bytes(), + }, + }, + ) + if err != nil { + return nil, err + } + + // Sign the packet. + signResp, err := t.SignVirtualPsbt( + ctx, &assetwalletrpc.SignVirtualPsbtRequest{ + FundedPsbt: fundResp.FundedPsbt, + }, + ) + if err != nil { + return nil, err + } + + return tappsbt.NewFromRawBytes( + bytes.NewReader(signResp.SignedPsbt), false, + ) +} + +// addP2WPKHOutputToPsbt adds a normal bitcoin P2WPKH output to a psbt for the +// given key and amount. +func addP2WPKHOutputToPsbt(packet *psbt.Packet, keyDesc keychain.KeyDescriptor, + amount btcutil.Amount, params *chaincfg.Params) error { + + derivation, _, _ := btcwallet.Bip32DerivationFromKeyDesc( + keyDesc, params.HDCoinType, + ) + + // Convert to Bitcoin address. + pubKeyBytes := keyDesc.PubKey.SerializeCompressed() + pubKeyHash := btcutil.Hash160(pubKeyBytes) + address, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, params) + if err != nil { + return err + } + + // Generate the P2WPKH scriptPubKey. + scriptPubKey, err := txscript.PayToAddrScript(address) + if err != nil { + return err + } + + // Add the output to the packet. + packet.UnsignedTx.AddTxOut( + wire.NewTxOut(int64(amount), scriptPubKey), + ) + + packet.Outputs = append(packet.Outputs, psbt.POutput{ + Bip32Derivation: []*psbt.Bip32Derivation{ + derivation, + }, + }) + + return nil +} + +// PrepareAndCommitVirtualPsbts prepares and commits virtual psbt to a BTC +// template so that the underlying wallet can fund the transaction and add the +// necessary additional input to pay for fees as well as a change output if the +// change keydescriptor is not provided. +func (t *TapdClient) PrepareAndCommitVirtualPsbts(ctx context.Context, + vpkt *tappsbt.VPacket, feeRateSatPerVByte chainfee.SatPerVByte, + changeKeyDesc *keychain.KeyDescriptor, params *chaincfg.Params, + sponsoringInputs []lndclient.LeaseDescriptor, + customLockID *wtxmgr.LockID, lockExpiration time.Duration) ( + *psbt.Packet, []*tappsbt.VPacket, []*tappsbt.VPacket, + *assetwalletrpc.CommitVirtualPsbtsResponse, error) { + + encodedVpkt, err := tappsbt.Encode(vpkt) + if err != nil { + return nil, nil, nil, nil, err + } + + btcPkt, err := tapsend.PrepareAnchoringTemplate( + []*tappsbt.VPacket{vpkt}, + ) + if err != nil { + return nil, nil, nil, nil, err + } + + for _, lease := range sponsoringInputs { + btcPkt.UnsignedTx.TxIn = append( + btcPkt.UnsignedTx.TxIn, &wire.TxIn{ + PreviousOutPoint: lease.Outpoint, + }, + ) + + btcPkt.Inputs = append(btcPkt.Inputs, psbt.PInput{ + WitnessUtxo: wire.NewTxOut( + int64(lease.Value), + lease.PkScript, + ), + }) + } + + commitRequest := &assetwalletrpc.CommitVirtualPsbtsRequest{ + Fees: &assetwalletrpc.CommitVirtualPsbtsRequest_SatPerVbyte{ + SatPerVbyte: uint64(feeRateSatPerVByte), + }, + AnchorChangeOutput: &assetwalletrpc.CommitVirtualPsbtsRequest_Add{ //nolint:lll + Add: true, + }, + VirtualPsbts: [][]byte{ + encodedVpkt, + }, + LockExpirationSeconds: uint64(lockExpiration.Seconds()), + } + + if customLockID != nil { + commitRequest.CustomLockId = (*customLockID)[:] + } + + if feeRateSatPerVByte == 0 { + commitRequest.SkipFunding = true + } + + if changeKeyDesc != nil { + err := addP2WPKHOutputToPsbt( + btcPkt, *changeKeyDesc, btcutil.Amount(1), params, + ) + if err != nil { + return nil, nil, nil, nil, err + } + + commitRequest.AnchorChangeOutput = + &assetwalletrpc.CommitVirtualPsbtsRequest_ExistingOutputIndex{ //nolint:lll + ExistingOutputIndex: 1, + } + } else { + commitRequest.AnchorChangeOutput = + &assetwalletrpc.CommitVirtualPsbtsRequest_Add{ + Add: true, + } + } + var buf bytes.Buffer + err = btcPkt.Serialize(&buf) + if err != nil { + return nil, nil, nil, nil, err + } + + commitRequest.AnchorPsbt = buf.Bytes() + + commitResponse, err := t.AssetWalletClient.CommitVirtualPsbts( + ctx, commitRequest, + ) + if err != nil { + return nil, nil, nil, nil, err + } + + fundedPacket, err := psbt.NewFromRawBytes( + bytes.NewReader(commitResponse.AnchorPsbt), false, + ) + if err != nil { + return nil, nil, nil, nil, err + } + + activePackets := make( + []*tappsbt.VPacket, len(commitResponse.VirtualPsbts), + ) + for idx := range commitResponse.VirtualPsbts { + activePackets[idx], err = tappsbt.Decode( + commitResponse.VirtualPsbts[idx], + ) + if err != nil { + return nil, nil, nil, nil, err + } + } + + passivePackets := make( + []*tappsbt.VPacket, len(commitResponse.PassiveAssetPsbts), + ) + for idx := range commitResponse.PassiveAssetPsbts { + passivePackets[idx], err = tappsbt.Decode( + commitResponse.PassiveAssetPsbts[idx], + ) + if err != nil { + return nil, nil, nil, nil, err + } + } + + return fundedPacket, activePackets, passivePackets, commitResponse, nil +} + +// LogAndPublish logs and publishes a psbt with the given active and passive +// assets. +func (t *TapdClient) LogAndPublish(ctx context.Context, btcPkt *psbt.Packet, + activeAssets []*tappsbt.VPacket, passiveAssets []*tappsbt.VPacket, + commitResp *assetwalletrpc.CommitVirtualPsbtsResponse, + skipBoradcast bool) (*taprpc.SendAssetResponse, error) { + + var buf bytes.Buffer + err := btcPkt.Serialize(&buf) + if err != nil { + return nil, err + } + + request := &assetwalletrpc.PublishAndLogRequest{ + AnchorPsbt: buf.Bytes(), + VirtualPsbts: make([][]byte, len(activeAssets)), + PassiveAssetPsbts: make([][]byte, len(passiveAssets)), + ChangeOutputIndex: commitResp.ChangeOutputIndex, + LndLockedUtxos: commitResp.LndLockedUtxos, + SkipAnchorTxBroadcast: skipBoradcast, + } + + for idx := range activeAssets { + request.VirtualPsbts[idx], err = tappsbt.Encode( + activeAssets[idx], + ) + if err != nil { + return nil, err + } + } + for idx := range passiveAssets { + request.PassiveAssetPsbts[idx], err = tappsbt.Encode( + passiveAssets[idx], + ) + if err != nil { + return nil, err + } + } + + resp, err := t.PublishAndLogTransfer(ctx, request) + if err != nil { + return nil, err + } + + return resp, nil +} + +// GetAssetBalance checks the balance of an asset by its ID. +func (t *TapdClient) GetAssetBalance(ctx context.Context, assetId []byte) ( + uint64, error) { + + // Check if we have enough funds to do the swap. + balanceResp, err := t.ListBalances( + ctx, &taprpc.ListBalancesRequest{ + GroupBy: &taprpc.ListBalancesRequest_AssetId{ + AssetId: true, + }, + AssetFilter: assetId, + }, + ) + if err != nil { + return 0, err + } + + // Check if we have enough funds to do the swap. + balance, ok := balanceResp.AssetBalances[hex.EncodeToString( + assetId, + )] + if !ok { + return 0, status.Error(codes.Internal, "internal error") + } + + return balance.Balance, nil +} + +// GetUnEncumberedAssetBalance returns the total balance of the given asset for +// which the given client owns the script keys. +func (t *TapdClient) GetUnEncumberedAssetBalance(ctx context.Context, + assetID []byte) (uint64, error) { + + allAssets, err := t.ListAssets(ctx, &taprpc.ListAssetRequest{}) + if err != nil { + return 0, err + } + + var balance uint64 + for _, a := range allAssets.Assets { + // Only count assets from the given asset ID. + if !bytes.Equal(a.AssetGenesis.AssetId, assetID) { + continue + } + + // Non-local means we don't have the internal key to spend the + // asset. + if !a.ScriptKeyIsLocal { + continue + } + + // If the asset is not declared known or has a script path, we + // can't spend it directly. + if !a.ScriptKeyDeclaredKnown || a.ScriptKeyHasScriptPath { + continue + } + + balance += a.Amount + } + + return balance, nil +} + +// DeriveNewKeys derives a new internal and script key. +func (t *TapdClient) DeriveNewKeys(ctx context.Context) (asset.ScriptKey, + keychain.KeyDescriptor, error) { + + scriptKeyDesc, err := t.NextScriptKey( + ctx, &assetwalletrpc.NextScriptKeyRequest{ + KeyFamily: uint32(asset.TaprootAssetsKeyFamily), + }, + ) + if err != nil { + return asset.ScriptKey{}, keychain.KeyDescriptor{}, err + } + + scriptKey, err := rpcutils.UnmarshalScriptKey(scriptKeyDesc.ScriptKey) + if err != nil { + return asset.ScriptKey{}, keychain.KeyDescriptor{}, err + } + + internalKeyDesc, err := t.NextInternalKey( + ctx, &assetwalletrpc.NextInternalKeyRequest{ + KeyFamily: uint32(asset.TaprootAssetsKeyFamily), + }, + ) + if err != nil { + return asset.ScriptKey{}, keychain.KeyDescriptor{}, err + } + internalKeyLnd, err := rpcutils.UnmarshalKeyDescriptor( + internalKeyDesc.InternalKey, + ) + if err != nil { + return asset.ScriptKey{}, keychain.KeyDescriptor{}, err + } + + return *scriptKey, internalKeyLnd, nil +} + +// ImportProof inserts the given proof to the local tapd instance's database. +func (t *TapdClient) ImportProof(ctx context.Context, p *proof.Proof) error { + var proofBytes bytes.Buffer + err := p.Encode(&proofBytes) + if err != nil { + return err + } + + asset := p.Asset + + proofType := universe.ProofTypeTransfer + if asset.IsGenesisAsset() { + proofType = universe.ProofTypeIssuance + } + + uniID := universe.Identifier{ + AssetID: asset.ID(), + ProofType: proofType, + } + if asset.GroupKey != nil { + uniID.GroupKey = &asset.GroupKey.GroupPubKey + } + + rpcUniID, err := tap.MarshalUniID(uniID) + if err != nil { + return err + } + + outpoint := &universerpc.Outpoint{ + HashStr: p.AnchorTx.TxHash().String(), + Index: int32(p.InclusionProof.OutputIndex), + } + + scriptKey := p.Asset.ScriptKey.PubKey + leafKey := &universerpc.AssetKey{ + Outpoint: &universerpc.AssetKey_Op{ + Op: outpoint, + }, + ScriptKey: &universerpc.AssetKey_ScriptKeyBytes{ + ScriptKeyBytes: scriptKey.SerializeCompressed(), + }, + } + + _, err = t.InsertProof(ctx, &universerpc.AssetProof{ + Key: &universerpc.UniverseKey{ + Id: rpcUniID, + LeafKey: leafKey, + }, + AssetLeaf: &universerpc.AssetLeaf{ + Proof: proofBytes.Bytes(), + }, + }) + + return err +} + +// ImportProofFile imports the proof file and returns the last proof. +func (t *TapdClient) ImportProofFile(ctx context.Context, rawProofFile []byte) ( + *proof.Proof, error) { + + proofFile, err := proof.DecodeFile(rawProofFile) + if err != nil { + return nil, err + } + + var lastProof *proof.Proof + + for i := 0; i < proofFile.NumProofs(); i++ { + lastProof, err = proofFile.ProofAt(uint32(i)) + if err != nil { + return nil, err + } + + err = t.ImportProof(ctx, lastProof) + if err != nil { + return nil, err + } + } + + return lastProof, nil +} + // getPaymentMaxAmount returns the milisat amount we are willing to pay for the // payment. func getPaymentMaxAmount(satAmount btcutil.Amount, feeLimitMultiplier float64) ( @@ -277,39 +767,147 @@ func getPaymentMaxAmount(satAmount btcutil.Amount, feeLimitMultiplier float64) ( ) } -func getClientConn(config *TapdConfig) (*grpc.ClientConn, error) { - // Load the specified TLS certificate and build transport credentials. - creds, err := credentials.NewClientTLSFromFile(config.TLSPath, "") - if err != nil { - return nil, err - } +// TapReceiveEvent is a struct that holds the information about a receive event. +type TapReceiveEvent struct { + // Outpoint is the anchor outpoint containing the confirmed asset. + Outpoint wire.OutPoint - // Load the specified macaroon file. - macBytes, err := os.ReadFile(config.MacaroonPath) + // ConfirmationHeight is the height at which the asset transfer was + // confirmed. + ConfirmationHeight uint32 +} + +// WaitForReceiveComplete waits for a receive to complete returning a channel +// that will notify the caller when the receive is complete. The addr is +// the address to filter for, and startTs is the timestamp from which to +// start receiving events. +func (t *TapdClient) WaitForReceiveComplete(ctx context.Context, addr string, + startTs time.Time) (<-chan TapReceiveEvent, <-chan error, error) { + + receiveEventsClient, err := t.SubscribeReceiveEvents( + ctx, &taprpc.SubscribeReceiveEventsRequest{ + FilterAddr: addr, + StartTimestamp: startTs.UnixMicro(), + }, + ) if err != nil { - return nil, err + return nil, nil, err } - mac := &macaroon.Macaroon{} - if err := mac.UnmarshalBinary(macBytes); err != nil { - return nil, err + + resChan := make(chan TapReceiveEvent) + errChan := make(chan error, 1) + + go func() { + for { + select { + case <-receiveEventsClient.Context().Done(): + panic(receiveEventsClient.Context().Err()) + default: + } + event, err := receiveEventsClient.Recv() + if err != nil { + errChan <- err + + return + } + + done, err := handleReceiveEvent(event, resChan) + if err != nil { + errChan <- err + + return + } + + if done { + return + } + } + }() + + return resChan, errChan, err +} + +func handleReceiveEvent(event *taprpc.ReceiveEvent, + resChan chan<- TapReceiveEvent) (bool, error) { + + switch event.Status { + case taprpc.AddrEventStatus_ADDR_EVENT_STATUS_TRANSACTION_DETECTED: + + case taprpc.AddrEventStatus_ADDR_EVENT_STATUS_TRANSACTION_CONFIRMED: + + case taprpc.AddrEventStatus_ADDR_EVENT_STATUS_COMPLETED: + outpoint, err := wire.NewOutPointFromString(event.Outpoint) + if err != nil { + return false, err + } + + resChan <- TapReceiveEvent{ + Outpoint: *outpoint, + ConfirmationHeight: event.ConfirmationHeight, + } + + return true, nil + + default: } - macaroon, err := macaroons.NewMacaroonCredential(mac) + return false, nil +} + +// TapSendEvent is a struct that holds the information about a send event. +type TapSendEvent struct { + Transfer *taprpc.AssetTransfer +} + +// WaitForSendComplete waits for a send to complete returning a channel that +// will notify the caller when the send is complete. The filterScriptKey is +// the script key of the asset to filter for, and the filterLabel is an +// optional label to filter the send events by. +func (t *TapdClient) WaitForSendComplete(ctx context.Context, + filterScriptKey []byte, filterLabel string) (<-chan TapSendEvent, + <-chan error, error) { + + sendEventsClient, err := t.SubscribeSendEvents( + ctx, &taprpc.SubscribeSendEventsRequest{ + FilterScriptKey: filterScriptKey, + FilterLabel: filterLabel, + }, + ) if err != nil { - return nil, err - } - // Create the DialOptions with the macaroon credentials. - opts := []grpc.DialOption{ - grpc.WithTransportCredentials(creds), - grpc.WithPerRPCCredentials(macaroon), - grpc.WithDefaultCallOptions(maxMsgRecvSize), + return nil, nil, err } - // Dial the gRPC server. - conn, err := grpc.Dial(config.Host, opts...) - if err != nil { - return nil, err + resChan := make(chan TapSendEvent) + errChan := make(chan error, 1) + + go func() { + for { + event, err := sendEventsClient.Recv() + if err != nil { + errChan <- err + + return + } + + if handleSendEvent(event, resChan) { + return + } + } + }() + + return resChan, errChan, nil +} + +func handleSendEvent(event *taprpc.SendEvent, + resChan chan<- TapSendEvent) bool { + + if event.SendState == tapfreighter.SendStateComplete.String() { + resChan <- TapSendEvent{ + Transfer: event.Transfer, + } + + return true } - return conn, nil + return false } diff --git a/assets/deposit/deposit.go b/assets/deposit/deposit.go new file mode 100644 index 000000000..f3fdb0657 --- /dev/null +++ b/assets/deposit/deposit.go @@ -0,0 +1,202 @@ +package deposit + +import ( + "encoding/hex" + "fmt" + "time" + + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/wtxmgr" + "github.com/lightninglabs/taproot-assets/proof" +) + +// State is the enum used for deposit states. +type State uint8 + +const ( + // StateInitiated indicates that the deposit has been initiated by the + // client. + StateInitiated State = 0 + + // StatePending indicates that the deposit is pending confirmation on + // the blockchain. + StatePending State = 1 + + // StateConfirmed indicates that the deposit has been confirmed on the + // blockchain. + StateConfirmed State = 2 + + // StateExpired indicates that the deposit has expired. + StateExpired State = 3 + + // StateTimeoutSweepPublished indicates that the timeout sweep has been + // published. + StateTimeoutSweepPublished State = 4 + + // StateWithdrawn indicates that the deposit has been withdrawn. + StateWithdrawn State = 5 + + // StateCooperativeSweepPublished indicates that the cooperative sweep + // withdrawing the deposit has been published. + StateCooperativeSweepPublished State = 6 + + // StateKeyRevealed indicates that the client has revealed a valid key + // for the deposit which is now ready to be swept. + StateKeyRevealed State = 7 + + // StateSpent indicates that the deposit has been spent. + StateSpent State = 8 + + // StateSwept indicates that the deposit has been swept, either by a + // timeout sweep or a cooperative (ie withdrawal) sweep. + StateSwept State = 9 +) + +// String coverts a deposit state to human readable string. +func (s State) String() string { + switch s { + case StateInitiated: + return "Initiated" + + case StatePending: + return "Pending" + + case StateConfirmed: + return "Confirmed" + + case StateExpired: + return "Expired" + + case StateTimeoutSweepPublished: + return "TimeoutSweepPublished" + + case StateWithdrawn: + return "Withdrawn" + + case StateCooperativeSweepPublished: + return "CooperativeSweepPublished" + + case StateKeyRevealed: + return "KeyRevealed" + + case StateSpent: + return "Spent" + + case StateSwept: + return "Swept" + + default: + return "Unknown" + } +} + +// IsFinal returns true if the deposit state is final, meaning that no further +// actions can be taken on the deposit. +func (s State) IsFinal() bool { + return s == StateSpent || s == StateSwept +} + +// DepositInfo holds publicly available information about an asset deposit. +// It is used to communicate deposit details to clients of the deposit Manager. +type DepositInfo struct { + // ID is the unique identifier for this deposit which will also be used + // to store the deposit in both the server and client databases. + ID string + + // Verison is the protocol version of the deposit. + Version AssetDepositProtocolVersion + + // CreatedAt is the time when the deposit was created (on the client). + CreatedAt time.Time + + // Amount is the amount of asset to be deposited. + Amount uint64 + + // Addr is the TAP deposit address where the asset will be sent. + Addr string + + // State is the deposit state. + State State + + // ConfirmationHeight is the block height at which the deposit was + // confirmed. + ConfirmationHeight uint32 + + // Outpoint is the anchor outpoint of the deposit. It is only set if the + // deposit has been confirmed. + Outpoint *wire.OutPoint + + // Expiry is the block height at which the deposit will expire. It is + // only set if the deposit has been confirmed. + Expiry uint32 + + // SweepAddr is the address we'll use to sweep the deposit back after + // timeout or if cooperatively withdrawing. + SweepAddr string +} + +// Copy creates a copy of the DepositInfo struct. +func (d *DepositInfo) Copy() *DepositInfo { + info := &DepositInfo{ + ID: d.ID, + Version: d.Version, + CreatedAt: d.CreatedAt, + Amount: d.Amount, + Addr: d.Addr, + State: d.State, + ConfirmationHeight: d.ConfirmationHeight, + Expiry: d.Expiry, + SweepAddr: d.SweepAddr, + } + + if d.Outpoint != nil { + info.Outpoint = &wire.OutPoint{ + Hash: d.Outpoint.Hash, + Index: d.Outpoint.Index, + } + } + + return info +} + +// Deposit is the struct that holds all the information about an asset deposit. +type Deposit struct { + *Kit + + *DepositInfo + + // PkScript is the pkscript of the deposit anchor output. + PkScript []byte + + // Proof is the proof of the deposit transfer. + Proof *proof.Proof + + // AnchorRootHash is the root hash of the deposit anchor output. + AnchorRootHash []byte +} + +// label returns a string label that we can use for marking a transfer funding +// the deposit. This is useful if we need to filter deposits. +func (d *Deposit) label() string { + return fmt.Sprintf("deposit %v", d.ID) +} + +// lockID converts a deposit ID to a lock ID. The lock ID is used to lock inputs +// used for the deposit sweep transaction. Note that we assume that the deposit +// ID is a hex-encoded string of the same length as the lock ID. +func (d *Deposit) lockID() (wtxmgr.LockID, error) { + var lockID wtxmgr.LockID + depositIDBytes, err := hex.DecodeString(d.ID) + if err != nil { + return wtxmgr.LockID{}, err + } + + if len(depositIDBytes) != len(lockID) { + return wtxmgr.LockID{}, fmt.Errorf("invalid deposit ID "+ + "length: %d", len(depositIDBytes)) + } + + copy(lockID[:], depositIDBytes) + + return lockID, nil +} diff --git a/assets/deposit/kit.go b/assets/deposit/kit.go new file mode 100644 index 000000000..b41a13ee1 --- /dev/null +++ b/assets/deposit/kit.go @@ -0,0 +1,505 @@ +package deposit + +import ( + "bytes" + "context" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/lightninglabs/lndclient" + "github.com/lightninglabs/loop/assets" + "github.com/lightninglabs/loop/assets/htlc" + "github.com/lightninglabs/taproot-assets/address" + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/commitment" + "github.com/lightninglabs/taproot-assets/proof" + "github.com/lightninglabs/taproot-assets/rpcutils" + "github.com/lightninglabs/taproot-assets/tappsbt" + "github.com/lightninglabs/taproot-assets/taprpc" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lntypes" +) + +// Kit is a struct that contains all the information needed to create +// and operator a 2-of-2 MuSig2 asset deposit. +type Kit struct { + // FunderScriptKey is a public key owned by the funder that acts as the + // script key for the deposit timeout sweep and as the key used when + // constructing the deposit spending zero fee HTLC. + FunderScriptKey *btcec.PublicKey + + // FunderInternalKey is a public key owned by the funder that is used + // for the derivation of the joint MuSig2 internal key of the deposit. + FunderInternalKey *btcec.PublicKey + + // CoSignerScriptKey is the script key of the counterparty who is + // co-signing the deposit funding and spending transactions. + CoSignerScriptKey *btcec.PublicKey + + // CoSignerKey is the internal key of the counterparty that is used for + // the derivation of the joint MuSig2 internal key of the deposit. + CoSignerInternalKey *btcec.PublicKey + + // KeyLocator is the locator of either the funder's or the co-signer's + // script key (depending on who is owning this Kit instance). If it is + // the funder's script key locator then it is used when signing a + // deposit timeout sweep transaction. + KeyLocator keychain.KeyLocator + + // AssetID is the identifier of the asset that will be held in the + // deposit. + AssetID asset.ID + + // CsvExpiry is the relative timelock in blocks for the timeout path of + // the deposit. + CsvExpiry uint32 + + // MuSig2Key is the aggregate key of the funder and co-signer. It is + // used as the internal key the output containing the deposit. + MuSig2Key *musig2.AggregateKey + + // chainParams is the chain parameters of the chain the deposit is + // being created on. + chainParams *address.ChainParams +} + +// NewKit creates a new deposit kit with the given funder key, co-signer +// key, key locator, asset ID and CSV expiry. +func NewKit(funderScriptKey, funderInternalKey, coSignerScriptKey, + coSignerInternalKey *btcec.PublicKey, keyLocator keychain.KeyLocator, + assetID asset.ID, csvExpiry uint32, chainParams *address.ChainParams) ( + *Kit, error) { + + sortKeys := true + muSig2Key, err := input.MuSig2CombineKeys( + input.MuSig2Version100RC2, + []*btcec.PublicKey{ + funderInternalKey, coSignerInternalKey, + }, + sortKeys, + &input.MuSig2Tweaks{ + TaprootBIP0086Tweak: true, + }, + ) + if err != nil { + return nil, err + } + + return &Kit{ + FunderScriptKey: funderScriptKey, + FunderInternalKey: funderInternalKey, + CoSignerScriptKey: coSignerScriptKey, + CoSignerInternalKey: coSignerInternalKey, + KeyLocator: keyLocator, + AssetID: assetID, + CsvExpiry: csvExpiry, + MuSig2Key: muSig2Key, + chainParams: chainParams, + }, nil +} + +// DeriveSharedDepositKey derives the internal key for the deposit from the +// passed public key by deriving a shared secret with the passed signer client. +func DeriveSharedDepositKey(ctx context.Context, + signer lndclient.SignerClient, pubKey *btcec.PublicKey) ( + *btcec.PublicKey, *btcec.PrivateKey, error) { + + secret, err := signer.DeriveSharedKey(ctx, pubKey, nil) + if err != nil { + return nil, nil, err + } + + derivedPrivKey, derivedPubKey := btcec.PrivKeyFromBytes(secret[:]) + + return derivedPubKey, derivedPrivKey, nil +} + +// GenTimeoutPathScript constructs a csv timeout script for the deposit funder. +// +// OP_CHECKSIGVERIFY OP_CHECKSEQUENCEVERIFY +func (d *Kit) GenTimeoutPathScript() ([]byte, error) { + builder := txscript.NewScriptBuilder() + + builder.AddData(schnorr.SerializePubKey(d.FunderScriptKey)) + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + builder.AddInt64(int64(d.CsvExpiry)) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + + return builder.Script() +} + +// genTimeoutPathSiblingPreimage generates the sibling preimage for the timeout +// path of the deposit. The sibling preimage is the preimage of the tap leaf +// that is the timeout path script. +func (d *Kit) genTimeoutPathSiblingPreimage() ([]byte, error) { + timeoutScript, err := d.GenTimeoutPathScript() + if err != nil { + return nil, err + } + + btcTapLeaf := txscript.TapLeaf{ + LeafVersion: txscript.BaseLeafVersion, + Script: timeoutScript, + } + + siblingPreimage, err := commitment.NewPreimageFromLeaf(btcTapLeaf) + if err != nil { + return nil, err + } + + preimageBytes, _, err := commitment.MaybeEncodeTapscriptPreimage( + siblingPreimage, + ) + if err != nil { + return nil, err + } + + return preimageBytes, nil +} + +// NewAddr creates a new deposit address to send funds to. The address is +// created with a MuSig2 key that is a combination of the funder and co-signer +// keys. The resulting anchor output will have a timeout path script that is a +// combination of the funder key and a CSV timelock. +func (d *Kit) NewAddr(ctx context.Context, funder *assets.TapdClient, + amount uint64) (*taprpc.Addr, error) { + + siblingPreimageBytes, err := d.genTimeoutPathSiblingPreimage() + if err != nil { + return nil, err + } + + tapScriptKey, _, _, _, err := htlc.CreateOpTrueLeaf() + if err != nil { + return nil, err + } + + btcInternalKey := d.MuSig2Key.PreTweakedKey + muSig2Addr, err := funder.NewAddr(ctx, &taprpc.NewAddrRequest{ + AssetId: d.AssetID[:], + Amt: amount, + ScriptKey: rpcutils.MarshalScriptKey(tapScriptKey), + InternalKey: &taprpc.KeyDescriptor{ + RawKeyBytes: btcInternalKey.SerializeCompressed(), + }, + TapscriptSibling: siblingPreimageBytes, + }) + if err != nil { + return nil, err + } + + return muSig2Addr, nil +} + +// IsMatchingAddr checks if the given address is a matching deposit address for +// the deposit kit. It checks that the address has the same internal key, script +// key and sibling preimage as the deposit address. Note that this function does +// not check the amount of the address. +func (d *Kit) IsMatchingAddr(addr string) (bool, error) { + tap, err := address.DecodeAddress(addr, d.chainParams) + if err != nil { + return false, err + } + + tapSciptKey, _, _, _, err := htlc.CreateOpTrueLeaf() + if err != nil { + return false, err + } + + keysMatch := tap.InternalKey.IsEqual(d.MuSig2Key.PreTweakedKey) && + tap.ScriptKey.IsEqual(tapSciptKey.PubKey) + + siblingPreimage1, err := d.genTimeoutPathSiblingPreimage() + if err != nil { + return false, err + } + + siblingPreimage2, _, err := commitment.MaybeEncodeTapscriptPreimage( + tap.TapscriptSibling, + ) + if err != nil { + return false, err + } + + return keysMatch && bytes.Equal(siblingPreimage1, siblingPreimage2), nil +} + +// GetMatchingOut checks if the given transfers contain a deposit output with +// the expected amount, script key and internal key. It returns the transfer +// and the index of the output if a match is found. If no match is found, it +// returns nil. +func (d *Kit) GetMatchingOut(amount uint64, transfers []*taprpc.AssetTransfer) ( + *taprpc.AssetTransfer, int, error) { + + // Prepare the tap scriptkey for the deposit. + tapSciptKey, _, _, _, err := htlc.CreateOpTrueLeaf() + if err != nil { + return nil, 0, err + } + scriptKey := tapSciptKey.PubKey.SerializeCompressed() + scriptKey[0] = secp256k1.PubKeyFormatCompressedEven + + // Prepare the sibling preimage for the deposit. + siblingPreimage, err := d.genTimeoutPathSiblingPreimage() + if err != nil { + return nil, 0, err + } + + internalKey := d.MuSig2Key.PreTweakedKey.SerializeCompressed() + + // Now iterate over all the transfers to find the deposit. + for _, transfer := range transfers { + for outIndex, out := range transfer.Outputs { + // First make sure that the script key matches. + if !bytes.Equal(out.ScriptKey, scriptKey) { + continue + } + + // Make sure that the internal key also matches. + if !bytes.Equal(out.Anchor.InternalKey, internalKey) { + continue + } + + // Double check that the sibling preimage also matches. + if !bytes.Equal( + out.Anchor.TapscriptSibling, + siblingPreimage, + ) { + + continue + } + + // Make sure the amount is as expected. + if out.Amount == amount { + return transfer, outIndex, nil + } + } + } + + return nil, 0, nil +} + +// NewHtlcAddr creates a new HTLC address with the same keys as the deposit. +// This is useful when we're creating an HTLC transaction spending the deposit. +func (d *Kit) NewHtlcAddr(ctx context.Context, + tapClient *assets.TapdClient, amount uint64, swapHash lntypes.Hash, + csvExpiry uint32) (*taprpc.Addr, *htlc.SwapKit, error) { + + s := htlc.SwapKit{ + SenderPubKey: d.FunderScriptKey, + ReceiverPubKey: d.CoSignerScriptKey, + AssetID: d.AssetID[:], + Amount: btcutil.Amount(amount), + SwapHash: swapHash, + CsvExpiry: csvExpiry, + } + + btcInternalKey, err := s.GetAggregateKey() + if err != nil { + return nil, nil, err + } + + siblingPreimage, err := s.GetSiblingPreimage() + if err != nil { + return nil, nil, err + } + + siblingPreimageBytes, _, err := commitment.MaybeEncodeTapscriptPreimage( + &siblingPreimage, + ) + if err != nil { + return nil, nil, err + } + + tapScriptKey, _, _, _, err := htlc.CreateOpTrueLeaf() + if err != nil { + return nil, nil, err + } + + htlcAddr, err := tapClient.NewAddr(ctx, &taprpc.NewAddrRequest{ + AssetId: d.AssetID[:], + Amt: amount, + ScriptKey: rpcutils.MarshalScriptKey(tapScriptKey), + InternalKey: &taprpc.KeyDescriptor{ + RawKeyBytes: btcInternalKey.SerializeCompressed(), + }, + TapscriptSibling: siblingPreimageBytes, + }) + if err != nil { + return nil, nil, err + } + + return htlcAddr, &s, nil +} + +// TapScriptKey generates a TAP script-key (the key of the script locking the +// asset) for the deposit. +func (d *Kit) TapScriptKey() (asset.ScriptKey, error) { + tapScriptKey, _, _, _, err := htlc.CreateOpTrueLeaf() + if err != nil { + return asset.ScriptKey{}, err + } + + return asset.NewScriptKey(tapScriptKey.PubKey), nil +} + +// ExportProof exports a proof for the deposit outpoint. The proof is used to +// prove that the deposit is valid and indeed happened. +func (d *Kit) ExportProof(ctx context.Context, funder *assets.TapdClient, + outpoint *wire.OutPoint) (*taprpc.ProofFile, error) { + + scriptKey, err := d.TapScriptKey() + if err != nil { + return nil, err + } + + return funder.ExportProof( + ctx, &taprpc.ExportProofRequest{ + AssetId: d.AssetID[:], + ScriptKey: scriptKey.PubKey.SerializeCompressed(), + Outpoint: &taprpc.OutPoint{ + Txid: outpoint.Hash[:], + OutputIndex: outpoint.Index, + }, + }, + ) +} + +// VerifyProof verifies that the given deposit proof is valid for the deposit +// address. It checks that the internal key of the proof matches the internal +// key of the deposit address and that the sibling preimage of the proof matches +// the sibling preimage of the deposit address. Returns the root hash of the +// anchor output if the proof is valid. +func (d *Kit) VerifyProof(depositProof *proof.Proof) ([]byte, error) { + // First generate a vpacket from the deposit proof. + proofVpacket, err := tappsbt.FromProofs( + []*proof.Proof{depositProof}, d.chainParams, tappsbt.V1, + ) + if err != nil { + return nil, err + } + + // Now verify that the proof is indeed for the deposit address. + input := proofVpacket.Inputs[0] + + // First check that the internal key of the proof matches the internal + // key of the deposit address. + anchorInternalKeyBytes := input.Anchor.InternalKey.SerializeCompressed() + depositInternalKey := d.MuSig2Key.PreTweakedKey.SerializeCompressed() + + if !bytes.Equal(depositInternalKey, anchorInternalKeyBytes) { + return nil, fmt.Errorf("VerifyProof: internal key mismatch") + } + + // Next check that the sibling preimage of the proof matches the sibling + // preimage of the deposit address. + depositSiblingPreimage, err := d.genTimeoutPathSiblingPreimage() + if err != nil { + return nil, err + } + + if !bytes.Equal(depositSiblingPreimage, input.Anchor.TapscriptSibling) { + return nil, fmt.Errorf("VerifyProof: sibling preimage mismatch") + } + + return input.Anchor.MerkleRoot, nil +} + +// GenTimeoutBtcControlBlock generates the control block for the timeout path of +// the deposit. +func (d *Kit) GenTimeoutBtcControlBlock(taprootAssetRoot []byte) ( + *txscript.ControlBlock, error) { + + internalKey := d.MuSig2Key.PreTweakedKey + + btcControlBlock := &txscript.ControlBlock{ + InternalKey: internalKey, + LeafVersion: txscript.BaseLeafVersion, + InclusionProof: taprootAssetRoot, + } + + timeoutPathScript, err := d.GenTimeoutPathScript() + if err != nil { + return nil, err + } + + rootHash := btcControlBlock.RootHash(timeoutPathScript) + tapKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash) + if tapKey.SerializeCompressed()[0] == + secp256k1.PubKeyFormatCompressedOdd { + + btcControlBlock.OutputKeyYIsOdd = true + } + + return btcControlBlock, nil +} + +// CreateTimeoutWitness creates a timeout witness for the deposit. +func (d *Kit) CreateTimeoutWitness(ctx context.Context, + signer lndclient.SignerClient, depositProof *proof.Proof, + sweepBtcPacket *psbt.Packet) (wire.TxWitness, error) { + + assetTxOut := sweepBtcPacket.Inputs[0].WitnessUtxo + feeTxOut := sweepBtcPacket.Inputs[1].WitnessUtxo + sweepBtcPacket.UnsignedTx.TxIn[0].Sequence = d.CsvExpiry + + timeoutScript, err := d.GenTimeoutPathScript() + if err != nil { + return nil, err + } + + signDesc := &lndclient.SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + KeyLocator: d.KeyLocator, + }, + SignMethod: input.TaprootScriptSpendSignMethod, + WitnessScript: timeoutScript, + Output: assetTxOut, + InputIndex: 0, + } + rawSigs, err := signer.SignOutputRaw( + ctx, sweepBtcPacket.UnsignedTx, + []*lndclient.SignDescriptor{ + signDesc, + }, + []*wire.TxOut{ + assetTxOut, feeTxOut, + }, + ) + if err != nil { + return nil, err + } + + taprootAssetRoot, err := assets.GenTaprootAssetRootFromProof( + depositProof, + ) + if err != nil { + return nil, err + } + + timeoutControlBlock, err := d.GenTimeoutBtcControlBlock( + taprootAssetRoot, + ) + if err != nil { + return nil, err + } + + controlBlockBytes, err := timeoutControlBlock.ToBytes() + if err != nil { + return nil, err + } + + return wire.TxWitness{ + rawSigs[0], + timeoutScript, + controlBlockBytes, + }, nil +} diff --git a/assets/deposit/log.go b/assets/deposit/log.go new file mode 100644 index 000000000..b9463ce46 --- /dev/null +++ b/assets/deposit/log.go @@ -0,0 +1,26 @@ +package deposit + +import ( + "github.com/btcsuite/btclog/v2" + "github.com/lightningnetwork/lnd/build" +) + +// Subsystem defines the sub system name of this package. +const Subsystem = "ADEP" + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger(Subsystem, nil)) +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/assets/deposit/manager.go b/assets/deposit/manager.go new file mode 100644 index 000000000..f2679f121 --- /dev/null +++ b/assets/deposit/manager.go @@ -0,0 +1,1249 @@ +package deposit + +import ( + "bytes" + "context" + "errors" + "fmt" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/wallet" + "github.com/lightninglabs/lndclient" + "github.com/lightninglabs/loop/assets" + "github.com/lightninglabs/loop/swapserverrpc" + "github.com/lightninglabs/taproot-assets/address" + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/taprpc" + "github.com/lightningnetwork/lnd/lntypes" +) + +var ( + // AssetDepositKeyFamily is the key family used for generating asset + // deposit keys. + AssetDepositKeyFamily = int32(1122) + + // ErrManagerShuttingDown signals that the asset deposit manager is + // shutting down and that no further calls should be made to it. + ErrManagerShuttingDown = errors.New("asset deposit manager is " + + "shutting down") + + // lockExpiration us the expiration time we use for sweep fee + // paying inputs. + lockExpiration = time.Hour * 24 +) + +// DepositUpdateCallback is a callback that is called when a deposit state is +// updated. The callback receives the updated deposit info. +type DepositUpdateCallback func(*DepositInfo) + +// Manager is responsible for creating, funding, sweeping and spending asset +// deposits used in asset swaps. It implements low level deposit management. +type Manager struct { + // depositServiceClient is a deposit service client. + depositServiceClient swapserverrpc.AssetDepositServiceClient + + // walletKit is the backing lnd wallet to use for deposit operations. + walletKit lndclient.WalletKitClient + + // signer is the signer client of the backing lnd wallet. + signer lndclient.SignerClient + + // chainNotifier is the chain notifier client of the underlyng lnd node. + chainNotifier lndclient.ChainNotifierClient + + // tapClient is the tapd client handling the deposit assets. + tapClient *assets.TapdClient + + // addressParams holds the TAP specific network params. + addressParams address.ChainParams + + // store is the deposit SQL store. + store *SQLStore + + // sweeper is responsible for assembling and publishing deposit sweeps. + sweeper *Sweeper + + // currentHeight is the current block height of the chain. + currentHeight uint32 + + // pendingSweeps is a map of all pending timeout sweeps. The key is the + // deposit ID. + pendingSweeps map[string]struct{} + + // deposits is a map of all active deposits. The key is the deposit ID. + deposits map[string]*Deposit + + // subscribers is a map of all registered deposit update subscribers. + // The key is the deposit ID. + subscribers map[string][]DepositUpdateCallback + + // callEnter is used to sequentialize calls to the batch handler's + // main event loop. + callEnter chan struct{} + + // callLeave is used to resume the execution flow of the batch handler's + // main event loop. + callLeave chan struct{} + + // criticalErrChan is used to signal that a critical error has occurred + // and that the manager should stop. + criticalErrChan chan error + + // quit is owned by the parent batcher and signals that the batch must + // stop. + quit chan struct{} + + // runCtx is a function that returns the Manager's run-loop context. + runCtx func() context.Context +} + +// NewManager constructs a new asset deposit manager. +func NewManager(depositServiceClient swapserverrpc.AssetDepositServiceClient, + walletKit lndclient.WalletKitClient, signer lndclient.SignerClient, + chainNotifier lndclient.ChainNotifierClient, + tapClient *assets.TapdClient, store *SQLStore, + params *chaincfg.Params) *Manager { + + addressParams := address.ParamsForChain(params.Name) + sweeper := NewSweeper(tapClient, walletKit, signer, addressParams) + + return &Manager{ + depositServiceClient: depositServiceClient, + walletKit: walletKit, + signer: signer, + chainNotifier: chainNotifier, + tapClient: tapClient, + store: store, + sweeper: sweeper, + addressParams: addressParams, + deposits: make(map[string]*Deposit), + pendingSweeps: make(map[string]struct{}), + subscribers: make(map[string][]DepositUpdateCallback), + callEnter: make(chan struct{}), + callLeave: make(chan struct{}), + criticalErrChan: make(chan error, 1), + quit: make(chan struct{}), + } +} + +// Run is the entry point running that starts up the deposit manager and also +// runs the main event loop. +func (m *Manager) Run(ctx context.Context, bestBlock uint32) error { + log.Infof("Starting asset deposit manager") + defer log.Infof("Asset deposit manager stopped") + + ctxc, cancel := context.WithCancel(ctx) + defer func() { + // Signal to the main event loop that it should stop. + close(m.quit) + cancel() + }() + + // Set the context getter. + m.runCtx = func() context.Context { + return ctxc + } + + m.currentHeight = bestBlock + + err := m.recoverDeposits(ctx) + if err != nil { + log.Errorf("Unable to recover deposits: %v", err) + + return err + } + + blockChan, blockErrChan, err := m.chainNotifier.RegisterBlockEpochNtfn( + ctxc, + ) + if err != nil { + log.Errorf("unable to register for block epoch notifications: "+ + "%v", err) + + return err + } + + // Wake the manager up very 10 seconds to check if there're any pending + // chores to do. + const wakeupInterval = time.Duration(10) * time.Second + withdrawTicker := time.NewTicker(wakeupInterval) + + for { + select { + case <-m.callEnter: + <-m.callLeave + + case blockHeight, ok := <-blockChan: + if !ok { + return nil + } + + log.Debugf("Received block epoch notification: %v", + blockHeight) + + m.currentHeight = uint32(blockHeight) + err := m.handleBlockEpoch(ctxc, m.currentHeight) + if err != nil { + return err + } + + case <-withdrawTicker.C: + err := m.publishPendingWithdrawals(ctx) + if err != nil { + log.Errorf("Unable to publish pending "+ + "withdrawals: %v", err) + + return err + } + + case err := <-blockErrChan: + log.Errorf("received error from block epoch "+ + "notification: %v", err) + + return err + + case err := <-m.criticalErrChan: + log.Errorf("stopping asset deposit manager due to "+ + "critical error: %v", err) + + return err + + case <-ctx.Done(): + return nil + + } + } +} + +// scheduleNextCall schedules the next call to the manager's main event loop. +// It returns a function that must be called when the call is finished. +func (m *Manager) scheduleNextCall() (func(), error) { + select { + case m.callEnter <- struct{}{}: + + case <-m.quit: + return func() {}, ErrManagerShuttingDown + } + + return func() { + m.callLeave <- struct{}{} + }, nil +} + +// criticalError is used to signal that a critical error has occurred. Such +// error will cause the manager to stop and return the (first) error to the +// caller of Run(...). +func (m *Manager) criticalError(err error) { + select { + case m.criticalErrChan <- err: + default: + } +} + +// recoverDeposits recovers all active deppsits when the deposit manager starts. +func (m *Manager) recoverDeposits(ctx context.Context) error { + // Fetch all active deposits from the store to kick-off the manager. + activeDeposits, err := m.store.GetActiveDeposits(ctx) + if err != nil { + log.Errorf("Unable to fetch deposits from store: %v", err) + + return err + } + + for i := range activeDeposits { + d := &activeDeposits[i] + log.Infof("Recovering deposit %v (state=%s)", d.ID, d.State) + + m.deposits[d.ID] = d + _, _, _, err = m.isDepositFunded(ctx, d) + if err != nil { + return err + } + + if d.State == StateInitiated { + // If the deposit has just been initiated, then we need + // to ensure that it is funded. + err = m.fundDepositIfNeeded(ctx, d) + if err != nil { + log.Errorf("Unable to fund deposit %v: %v", + d.ID, err) + + return err + } + } else { + // Cache proof info of the deposit in-memory. + err = m.cacheProofInfo(ctx, d) + if err != nil { + return err + } + } + } + + return nil +} + +// handleBlockEpoch is called when a new block is added to the chain. +func (m *Manager) handleBlockEpoch(ctx context.Context, height uint32) error { + for _, d := range m.deposits { + if d.State != StateConfirmed { + continue + } + + log.Debugf("Checking if deposit %v is expired, expiry=%v", d.ID, + d.ConfirmationHeight+d.CsvExpiry) + + if height < d.ConfirmationHeight+d.CsvExpiry { + continue + } + + err := m.handleDepositExpired(ctx, d) + if err != nil { + log.Errorf("Unable to update deposit %v state: %v", + d.ID, err) + + return err + } + } + + // Now publish the timeout sweeps for all expired deposits and also + // move them to the pending sweeps map. + for _, d := range m.deposits { + // TODO(bhandras): republish will insert a new transfer entry in + // tapd, despite the transfer already existing. To avoid that, + // we won't re-publish the timeout sweep for now. + if d.State != StateExpired { + continue + } + + err := m.publishTimeoutSweep(ctx, d) + if err != nil { + return err + } + } + + return nil +} + +// GetBestBlock returns the current best block height of the chain. +func (m *Manager) GetBestBlock() (uint32, error) { + done, err := m.scheduleNextCall() + if err != nil { + return 0, err + } + defer done() + + return m.currentHeight, nil +} + +// SubscribeDepositUpdates registers a subscriber for deposit state updates. +func (m *Manager) SubscribeDepositUpdates(depositID string, + subscriber DepositUpdateCallback) error { + + done, err := m.scheduleNextCall() + if err != nil { + return err + } + defer done() + + d, ok := m.deposits[depositID] + if !ok { + return fmt.Errorf("deposit %s not found", depositID) + } + + log.Infof("Registering deposit state update subscriber: %s", d.ID) + + // Note that for simplicity of design we do not check whether a + // subscriber is already registered for the deposit. + m.subscribers[d.ID] = append(m.subscribers[d.ID], subscriber) + + // Send the current deposit info to the subscriber right away. + subscriber(d.DepositInfo.Copy()) + + return nil +} + +// handleDepositStateUpdate updates the deposit state in the store and notifies +// all subscribers of the deposit state change. +func (m *Manager) handleDepositStateUpdate(ctx context.Context, + d *Deposit) error { + + log.Infof("Handling deposit state update: %s, state=%v", d.ID, d.State) + + // Store the deposit state update in the database. + err := m.store.UpdateDeposit(ctx, d) + if err != nil { + return err + } + + // Notify all subscribers of the deposit state update. + subscribers, ok := m.subscribers[d.ID] + if !ok { + log.Debugf("No subscribers for deposit %s", d.ID) + return nil + } + + for _, subscriber := range subscribers { + go subscriber(d.DepositInfo.Copy()) + } + + return nil +} + +// NewDeposit creates a new asset deposit with the given parameters. +func (m *Manager) NewDeposit(ctx context.Context, assetID asset.ID, + amount uint64, csvExpiry uint32) (DepositInfo, error) { + + clientKeyDesc, err := m.walletKit.DeriveNextKey( + ctx, AssetDepositKeyFamily, + ) + if err != nil { + return DepositInfo{}, err + } + clientInternalPubKey, _, err := DeriveSharedDepositKey( + ctx, m.signer, clientKeyDesc.PubKey, + ) + if err != nil { + return DepositInfo{}, err + } + + clientScriptPubKeyBytes := clientKeyDesc.PubKey.SerializeCompressed() + clientInternalPubKeyBytes := clientInternalPubKey.SerializeCompressed() + + resp, err := m.depositServiceClient.NewAssetDeposit( + ctx, &swapserverrpc.NewAssetDepositServerReq{ + AssetId: assetID[:], + Amount: amount, + ClientInternalPubkey: clientInternalPubKeyBytes, + ClientScriptPubkey: clientScriptPubKeyBytes, + CsvExpiry: int32(csvExpiry), + }, + ) + if err != nil { + log.Errorf("Swap server was unable to create the deposit: %v", + err) + + return DepositInfo{}, err + } + + serverScriptPubKey, err := btcec.ParsePubKey(resp.ServerScriptPubkey) + if err != nil { + return DepositInfo{}, err + } + + serverInternalPubKey, err := btcec.ParsePubKey( + resp.ServerInternalPubkey, + ) + if err != nil { + return DepositInfo{}, err + } + + kit, err := NewKit( + clientKeyDesc.PubKey, clientInternalPubKey, serverScriptPubKey, + serverInternalPubKey, clientKeyDesc.KeyLocator, assetID, + csvExpiry, &m.addressParams, + ) + if err != nil { + return DepositInfo{}, err + } + + deposit := &Deposit{ + Kit: kit, + DepositInfo: &DepositInfo{ + ID: resp.DepositId, + Version: CurrentProtocolVersion(), + CreatedAt: time.Now(), + Amount: amount, + Addr: resp.DepositAddr, + State: StateInitiated, + }, + } + + err = m.store.AddAssetDeposit(ctx, deposit) + if err != nil { + log.Errorf("Unable to add deposit to store: %v", err) + + return DepositInfo{}, err + } + + err = m.handleNewDeposit(ctx, deposit) + if err != nil { + log.Errorf("Unable to add deposit to active deposits: %v", err) + + return DepositInfo{}, err + } + + return *deposit.DepositInfo.Copy(), nil +} + +// handleNewDeposit adds the deposit to the active deposits map and starts the +// funding process, all on the main event loop goroutine. +func (m *Manager) handleNewDeposit(ctx context.Context, deposit *Deposit) error { + done, err := m.scheduleNextCall() + if err != nil { + return err + } + defer done() + + m.deposits[deposit.ID] = deposit + + return m.fundDepositIfNeeded(ctx, deposit) +} + +// fundDepositIfNeeded attempts to fund the passed deposit if it is not already +// funded. +func (m *Manager) fundDepositIfNeeded(ctx context.Context, d *Deposit) error { + // Now list transfers from tapd and check if the deposit is funded. + funded, transfer, outIndex, err := m.isDepositFunded(ctx, d) + if err != nil { + log.Errorf("Unable to check if deposit %v is funded: %v", d.ID, + err) + + return err + } + + if !funded { + // No funding transfer found, so we'll attempt to fund the + // deposit by sending the asset to the deposit address. Note + // that we label the send request with a specific label in order + // to be able to subscribe to send events with a label filter. + sendResp, err := m.tapClient.SendAsset( + ctx, &taprpc.SendAssetRequest{ + TapAddrs: []string{d.Addr}, + Label: d.label(), + }, + ) + if err != nil { + log.Errorf("Unable to send asset to deposit %v: %v", + d.ID, err) + + return err + } + + // Extract the funding outpoint from the transfer. + transfer, outIndex, err = d.GetMatchingOut( + d.Amount, []*taprpc.AssetTransfer{sendResp.Transfer}, + ) + if err != nil { + log.Errorf("Unable to get funding out for %v: %v ", + d.ID, err) + + return err + } + } + + log.Infof("Deposit %v is funded in anchor %x:%d, "+ + "anchor tx block height: %v", d.ID, + transfer.AnchorTxHash, outIndex, transfer.AnchorTxBlockHeight) + + // If the deposit is confirmed, then we don't need to wait for the + // confirmation to happen. + // TODO(bhandras): once backlog events are supported we can remove this. + if transfer.AnchorTxBlockHeight != 0 { + return m.markDepositConfirmed(ctx, d, transfer) + } + + // Wait for deposit confirmation otherwise. + err = m.waitForDepositConfirmation(m.runCtx(), d) + if err != nil { + log.Errorf("Unable to wait for deposit confirmation: %v", err) + + return err + } + + return nil +} + +// isDepositFunded checks if the deposit is funded with the expected amount. It +// does so by checking if there is a deposit output with the expected keys and +// amount in the list of transfers of the funder. +func (m *Manager) isDepositFunded(ctx context.Context, d *Deposit) (bool, + *taprpc.AssetTransfer, int, error) { + + res, err := m.tapClient.ListTransfers( + ctx, &taprpc.ListTransfersRequest{}, + ) + if err != nil { + return false, nil, 0, err + } + + transfer, outIndex, err := d.GetMatchingOut(d.Amount, res.Transfers) + if err != nil { + return false, nil, 0, err + } + + if transfer == nil { + return false, nil, 0, nil + } + + return true, transfer, outIndex, nil +} + +// waitForDepositConfirmation waits for the deposit to be confirmed. +// +// NOTE: currently SubscribeSendEvents does not support streaming backlog +// events. To avoid missing the confirmation event, we also poll asset transfers +// upon restart. +func (m *Manager) waitForDepositConfirmation(ctx context.Context, + d *Deposit) error { + + log.Infof("Subscribing to send events for pending deposit %s, "+ + "addr=%v, created_at=%v", d.ID, d.Addr, d.CreatedAt) + + resChan, errChan, err := m.tapClient.WaitForSendComplete( + ctx, nil, d.label(), + ) + if err != nil { + log.Errorf("unable to subscribe to send events: %v", err) + return err + } + + go func() { + select { + case res := <-resChan: + done, err := m.scheduleNextCall() + if err != nil { + log.Errorf("Unable to schedule next call: %v", + err) + + m.criticalError(err) + } + defer done() + + err = m.markDepositConfirmed(ctx, d, res.Transfer) + if err != nil { + log.Errorf("Unable to mark deposit %v as "+ + "confirmed: %v", d.ID, err) + + m.criticalError(err) + } + + case err := <-errChan: + m.criticalError(err) + } + }() + + return nil +} + +// cacheProofInfo caches the proof information for the deposit in-memory. +func (m *Manager) cacheProofInfo(ctx context.Context, d *Deposit) error { + proofFile, err := d.ExportProof(ctx, m.tapClient, d.Outpoint) + if err != nil { + log.Errorf("Unable to export proof for deposit %v: %v", d.ID, + err) + + return err + } + + // Import the proof in order to be able to spend the deposit later on + // either into an HTLC or a timeout sweep. + // + // TODO(bhandras): do we need to check/handle if/when the proof is + // already imported? + depositProof, err := m.tapClient.ImportProofFile( + ctx, proofFile.RawProofFile, + ) + if err != nil { + return err + } + + d.Proof = depositProof + + // Verify that the proof is valid for the deposit and get the root hash + // which we may use later when signing the HTLC transaction. + anchorRootHash, err := d.VerifyProof(depositProof) + if err != nil { + log.Errorf("failed to verify deposity proof: %v", err) + + return err + } + + d.AnchorRootHash = anchorRootHash + + return nil +} + +// markDepositConfirmed marks the deposit as confirmed in the store and moves it +// to the active deposits map. It also updates the outpoint and the confirmation +// height of the deposit. +func (m *Manager) markDepositConfirmed(ctx context.Context, d *Deposit, + transfer *taprpc.AssetTransfer) error { + + // Extract the funding outpoint from the transfer. + _, outIdx, err := d.GetMatchingOut( + d.Amount, []*taprpc.AssetTransfer{transfer}, + ) + if err != nil { + return err + } + + outpoint, err := wire.NewOutPointFromString( + transfer.Outputs[outIdx].Anchor.Outpoint, + ) + if err != nil { + log.Errorf("Unable to parse deposit outpoint %v: %v", + transfer.Outputs[outIdx].Anchor.Outpoint, err) + + return err + } + + d.Outpoint = outpoint + d.PkScript = transfer.Outputs[outIdx].Anchor.PkScript + d.ConfirmationHeight = transfer.AnchorTxBlockHeight + d.State = StateConfirmed + + err = m.handleDepositStateUpdate(ctx, d) + if err != nil { + return err + } + + err = m.cacheProofInfo(ctx, d) + if err != nil { + return err + } + + log.Infof("Deposit %v is confirmed at block %v", d.ID, + d.ConfirmationHeight) + + return nil +} + +// ListDeposits returns all deposits that are in the given range of +// confirmations. +func (m *Manager) ListDeposits(ctx context.Context, minConfs, maxConfs uint32) ( + []Deposit, error) { + + bestBlock, err := m.GetBestBlock() + if err != nil { + return nil, err + } + + deposits, err := m.store.GetAllDeposits(ctx) + if err != nil { + return nil, err + } + + // Only filter based on confirmations if the user has set a min or max + // confs. + filterConfs := minConfs != 0 || maxConfs != 0 + + // Prefilter deposits based on the min/max confs. + filteredDeposits := make([]Deposit, 0, len(deposits)) + for _, deposit := range deposits { + if filterConfs { + // Check that the deposit suits our min/max confs + // criteria. + confs := bestBlock - deposit.ConfirmationHeight + if confs < minConfs || confs > maxConfs { + continue + } + } + + filteredDeposits = append(filteredDeposits, deposit) + } + + return filteredDeposits, nil +} + +// handleDepositStateUpdate updates the deposit state in the store and +// notifies all subscribers of the deposit state change. +func (m *Manager) handleDepositExpired(ctx context.Context, d *Deposit) error { + // Generate a new address for the timeout sweep. + rpcTimeoutSweepAddr, err := m.tapClient.NewAddr( + ctx, &taprpc.NewAddrRequest{ + AssetId: d.AssetID[:], + Amt: d.Amount, + }, + ) + if err != nil { + log.Errorf("Unable to create timeout sweep address: %v", err) + + return err + } + + d.State = StateExpired + d.SweepAddr = rpcTimeoutSweepAddr.Encoded + + return m.handleDepositStateUpdate(ctx, d) +} + +// publishTimeoutSweep publishes a timeout sweep for the deposit. As we use the +// same lock ID for the sponsoring inputs, it's possible to republish the sweep +// however it'll create a new transfer entry in tapd, which we want to avoid +// (for now). +func (m *Manager) publishTimeoutSweep(ctx context.Context, d *Deposit) error { + // Start monitoring the sweep unless we're already doing so. + if _, ok := m.pendingSweeps[d.ID]; !ok { + err := m.waitForDepositSpend(ctx, d) + if err != nil { + log.Errorf("Unable to wait for deposit %v spend: %v", + d.ID, err) + + return err + } + + m.pendingSweeps[d.ID] = struct{}{} + } + + log.Infof("(Re)publishing timeout sweep for deposit %v", d.ID) + + // TODO(bhandras): conf target should be dynamic/configrable. + const confTarget = 2 + feeRateSatPerKw, err := m.walletKit.EstimateFeeRate( + ctx, confTarget, + ) + + lockID, err := d.lockID() + if err != nil { + return err + } + + sweepAddr, err := address.DecodeAddress(d.SweepAddr, &m.addressParams) + if err != nil { + log.Errorf("Unable to decode timeout sweep address: %v", err) + + return err + } + + snedResp, err := m.sweeper.PublishDepositTimeoutSweep( + ctx, d.Kit, d.Proof, sweepAddr, feeRateSatPerKw.FeePerVByte(), + lockID, lockExpiration, + ) + if err != nil { + // TOOD(bhandras): handle republish errors. + log.Infof("Unable to publish timeout sweep for deposit %v: %v", + d.ID, err) + } else { + log.Infof("Published timeout sweep for deposit %v: %x", d.ID, + snedResp.Transfer.AnchorTxHash) + + // Update deposit state on first successful publish. + if d.State != StateTimeoutSweepPublished { + d.State = StateTimeoutSweepPublished + + err = m.handleDepositStateUpdate(ctx, d) + if err != nil { + log.Errorf("Unable to update deposit %v "+ + "state: %v", d.ID, err) + + return err + } + } + } + + return nil +} + +// waitForDepositSpend waits for the deposit to be spent. It subscribes to +// receive events for the deposit's sweep address notifying us once the transfer +// has completed. +func (m *Manager) waitForDepositSpend(ctx context.Context, d *Deposit) error { + log.Infof("Waiting for deposit spend: %s, sweep_addr=%v, created_at=%v", + d.ID, d.SweepAddr, d.CreatedAt) + + resChan, errChan, err := m.tapClient.WaitForReceiveComplete( + ctx, d.SweepAddr, d.CreatedAt, + ) + + if err != nil { + log.Errorf("unable to subscribe to receive events: %v", err) + + return err + } + + go func() { + select { + case res := <-resChan: + // At this point we can consider the deposit confirmed. + err = m.handleDepositSpend( + ctx, d, res.Outpoint.String(), + ) + if err != nil { + m.criticalError(err) + } + + case err := <-errChan: + m.criticalError(err) + } + }() + + return nil +} + +// handleDepositSpend is called when the deposit is spent. It updates the +// deposit state and releases the inputs used for the deposit sweep. +func (m *Manager) handleDepositSpend(ctx context.Context, d *Deposit, + outpoint string) error { + + done, err := m.scheduleNextCall() + if err != nil { + log.Errorf("Unable to schedule next call: %v", err) + + return err + } + defer done() + + switch d.State { + case StateTimeoutSweepPublished: + fallthrough + case StateCooperativeSweepPublished: + log.Infof("Deposit %s withdrawn in: %s", d.ID, outpoint) + d.State = StateSwept + + err := m.releaseDepositSweepInputs(ctx, d) + if err != nil { + log.Errorf("Unable to release deposit sweep inputs: "+ + "%v", err) + + return err + } + + default: + err := fmt.Errorf("Spent deposit %s in unexpected state %s", + d.ID, d.State) + + log.Errorf(err.Error()) + + return err + } + + // TODO(bhandras): should save the spend details to the store? + err = m.handleDepositStateUpdate(ctx, d) + if err != nil { + log.Errorf("Unable to update deposit %v state: %v", d.ID, err) + + return err + } + + // Sanity check that the deposit is in the pending sweeps map. + if _, ok := m.pendingSweeps[d.ID]; !ok { + log.Errorf("Deposit %v not found in pending deposits", d.ID) + } + + // We can now remove the deposit from the pending sweeps map as we don't + // need to monitor for the spend anymore. + delete(m.pendingSweeps, d.ID) + + return nil +} + +// releaseDepositSweepInputs releases the inputs that were used for the deposit +// sweep. +func (m *Manager) releaseDepositSweepInputs(ctx context.Context, + d *Deposit) error { + + lockID, err := d.lockID() + if err != nil { + return err + } + + leases, err := m.walletKit.ListLeases(ctx) + if err != nil { + return err + } + + for _, lease := range leases { + if lease.LockID != lockID { + continue + } + + // Unlock any UTXOs that were used for the deposit sweep. + err = m.walletKit.ReleaseOutput(ctx, lockID, lease.Outpoint) + if err != nil { + return err + } + } + + return nil +} + +// WithdrawDeposits withdraws the deposits with the given IDs. It will first ask +// the server for the deposit keys, then initate the withdrawal by updating the +// deposit state. +func (m *Manager) WithdrawDeposits(ctx context.Context, + depositIDs []string) error { + + done, err := m.scheduleNextCall() + if err != nil { + return err + } + defer done() + + for _, depositID := range depositIDs { + d, ok := m.deposits[depositID] + if !ok { + return fmt.Errorf("deposit %v not found", depositID) + } + + if d.State != StateConfirmed { + return fmt.Errorf("deposit %v is not withdrawable, "+ + "current state: %v", depositID, d.State) + } + + log.Infof("Initiating deposit withdrawal %v: %v", + depositID, d.Amount) + } + + keys, err := m.depositServiceClient.WithdrawAssetDeposits( + ctx, &swapserverrpc.WithdrawAssetDepositsServerReq{ + DepositIds: depositIDs, + }, + ) + if err != nil { + return fmt.Errorf("unable to request withdrawal: %w", err) + } + + for depositID, privKeyBytes := range keys.DepositKeys { + d, ok := m.deposits[depositID] + if !ok { + log.Warnf("Skipping withdrawal of unknown deposit: %v", + depositID) + continue + } + + privKey, pubKey := btcec.PrivKeyFromBytes(privKeyBytes) + if !d.CoSignerInternalKey.IsEqual(pubKey) { + return fmt.Errorf("revealed co-signer internal key "+ + "does not match local key for %v", depositID) + } + + err := m.store.SetAssetDepositServerKey(ctx, depositID, privKey) + if err != nil { + return err + } + + rpcSweepAddr, err := m.tapClient.NewAddr( + ctx, &taprpc.NewAddrRequest{ + AssetId: d.AssetID[:], + Amt: d.Amount, + }, + ) + if err != nil { + return err + } + + d.State = StateWithdrawn + d.SweepAddr = rpcSweepAddr.Encoded + err = m.handleDepositStateUpdate(ctx, d) + if err != nil { + return err + } + } + + return nil +} + +// publishPendingWithdrawals publishes any pending deposit withdrawals. +func (m *Manager) publishPendingWithdrawals(ctx context.Context) error { + for _, d := range m.deposits { + // TODO(bhandras): republish on StateCooperativeSweepPublished. + if d.State != StateWithdrawn { + continue + } + + // Start monitoring the sweep unless we're already doing so. + if _, ok := m.pendingSweeps[d.ID]; !ok { + err := m.waitForDepositSpend(ctx, d) + if err != nil { + log.Errorf("Unable to wait for deposit %v "+ + "spend: %v", d.ID, err) + + return err + } + + m.pendingSweeps[d.ID] = struct{}{} + } + + serverKey, err := m.store.GetAssetDepositServerKey( + ctx, d.ID, + ) + if err != nil { + return err + } + + lockID, err := d.lockID() + if err != nil { + return err + } + + sweepAddr, err := address.DecodeAddress( + d.SweepAddr, &m.addressParams, + ) + if err != nil { + return err + } + + // TODO(bhandras): conf target should be dynamic/configrable. + const confTarget = 2 + feeRateSatPerKw, err := m.walletKit.EstimateFeeRate( + ctx, confTarget, + ) + + funder := true + sendAssetResp, err := m.sweeper.PublishDepositSweepMuSig2( + ctx, d.Kit, funder, d.Proof, serverKey, sweepAddr, + feeRateSatPerKw.FeePerVByte(), lockID, lockExpiration, + ) + if err != nil { + log.Errorf("Unable to publish deposit sweep for %v: %v", + d.ID, err) + } else { + log.Infof("Published sweep for deposit %v: %v", d.ID, + sendAssetResp.Transfer.AnchorTxHash) + + d.State = StateCooperativeSweepPublished + err = m.handleDepositStateUpdate(ctx, d) + if err != nil { + log.Errorf("Unable to update deposit %v "+ + "state: %v", d.ID, err) + + return err + } + + } + } + + return nil +} + +// RevealDepositKeys reveals the internal keys for the given deposit IDs to +// the swap server. +func (m *Manager) RevealDepositKeys(ctx context.Context, + depositIDs []string) error { + + done, err := m.scheduleNextCall() + if err != nil { + return err + } + defer done() + + // First check that all requested deposits are in the required state and + // collect the keys. + keys := make(map[string][]byte, len(depositIDs)) + for _, depositID := range depositIDs { + d, ok := m.deposits[depositID] + if !ok { + log.Warnf("Can't reveal key for deposit %v as it is "+ + "not active", depositID) + } + + if d.State != StateConfirmed && d.State != StateKeyRevealed { + return fmt.Errorf("deposit %v key cannot be revealed", + depositID) + } + + internalPubKey, internalPrivKey, err := DeriveSharedDepositKey( + ctx, m.signer, d.FunderScriptKey, + ) + if err != nil { + return err + } + + if !d.FunderInternalKey.IsEqual(internalPubKey) { + log.Errorf("Funder internal key %x does not match "+ + "expected %x for deposit %v", + d.FunderInternalKey.SerializeCompressed(), + internalPubKey.SerializeCompressed(), depositID) + + return fmt.Errorf("funder internal key mismatch") + } + + keys[depositID] = internalPrivKey.Serialize() + } + + // Update the deposit state before we actually push the keys to the + // server. Otherwise we may fail to update the state in our database, + // despite the server accepting the keys. + for depositID := range keys { + d := m.deposits[depositID] + d.State = StateKeyRevealed + err = m.handleDepositStateUpdate(ctx, d) + if err != nil { + return err + } + + log.Infof("Revealing deposit key for %v: pub=%x", depositID, + d.FunderInternalKey.SerializeCompressed()) + } + + // Now push the keys to the server. + _, err = m.depositServiceClient.PushAssetDepositKeys( + ctx, &swapserverrpc.PushAssetDepositKeysReq{ + DepositKeys: keys, + }, + ) + if err != nil { + return err + } + + return err +} + +// CoSignHTLC will partially a deposit spending zero-fee HTLC and send the +// resulting signature to the swap server. +func (m *Manager) CoSignHTLC(ctx context.Context, depositID string, + serverNonce [musig2.PubNonceSize]byte, hash lntypes.Hash, + csvExpiry uint32) error { + + done, err := m.scheduleNextCall() + if err != nil { + return err + } + defer done() + + deposit, ok := m.deposits[depositID] + if !ok { + return fmt.Errorf("deposit %v not available", depositID) + } + + _, htlcPkt, _, _, _, err := m.sweeper.GetHTLC( + ctx, deposit.Kit, deposit.Proof, deposit.Amount, hash, + csvExpiry, + ) + if err != nil { + log.Errorf("Unable to get HTLC packet: %v", err) + + return err + } + + prevOutFetcher := wallet.PsbtPrevOutputFetcher(htlcPkt) + sigHash, err := getSigHash(htlcPkt.UnsignedTx, 0, prevOutFetcher) + if err != nil { + return err + } + + nonce, partialSig, err := m.sweeper.PartialSignMuSig2( + ctx, deposit.Kit, deposit.AnchorRootHash, serverNonce, sigHash, + ) + if err != nil { + log.Errorf("Unable to partial sign HTLC: %v", err) + + return err + } + + var pktBuf bytes.Buffer + err = htlcPkt.Serialize(&pktBuf) + if err != nil { + log.Errorf("Unable to write HTLC packet: %v", err) + return err + } + + _, err = m.depositServiceClient.PushAssetDepositHtlcSigs( + ctx, &swapserverrpc.PushAssetDepositHtlcSigsReq{ + PartialSigs: []*swapserverrpc.AssetDepositPartialSig{ + { + DepositId: depositID, + Nonce: nonce[:], + PartialSig: partialSig, + }, + }, + HtlcPsbt: pktBuf.Bytes(), + }, + ) + + return err +} diff --git a/assets/deposit/protocol.go b/assets/deposit/protocol.go new file mode 100644 index 000000000..3223e8cea --- /dev/null +++ b/assets/deposit/protocol.go @@ -0,0 +1,44 @@ +package deposit + +// AssetDepositProtocolVersion represents the protocol version for asset +// deposits. +type AssetDepositProtocolVersion uint32 + +const ( + // ProtocolVersion_V0 indicates that the client is a legacy version + // that did not report its protocol version. + ProtocolVersion_V0 AssetDepositProtocolVersion = 0 + + // stableProtocolVersion defines the current stable RPC protocol + // version. + stableProtocolVersion = ProtocolVersion_V0 +) + +var ( + // currentRPCProtocolVersion holds the version of the RPC protocol + // that the client selected to use for new swaps. Shouldn't be lower + // than the previous protocol version. + currentRPCProtocolVersion = stableProtocolVersion +) + +// CurrentRPCProtocolVersion returns the RPC protocol version selected to be +// used for new swaps. +func CurrentProtocolVersion() AssetDepositProtocolVersion { + return currentRPCProtocolVersion +} + +// Valid returns true if the value of the AddressProtocolVersion is valid. +func (p AssetDepositProtocolVersion) Valid() bool { + return p <= AssetDepositProtocolVersion(stableProtocolVersion) +} + +// String returns the string representation of a protocol version. +func (p AssetDepositProtocolVersion) String() string { + switch p { + case ProtocolVersion_V0: + return "ASSET_DEPOSIT_V0" + + default: + return "Unknown" + } +} diff --git a/assets/deposit/server.go b/assets/deposit/server.go new file mode 100644 index 000000000..c92f44dd9 --- /dev/null +++ b/assets/deposit/server.go @@ -0,0 +1,190 @@ +package deposit + +import ( + "context" + "encoding/hex" + "fmt" + + "github.com/lightninglabs/loop/looprpc" + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightningnetwork/lnd/lntypes" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var ( + // ErrAssetDepositsUnavailable is returned when the asset deposit + // service is not available. + ErrAssetDepositsUnavailable = status.Error(codes.Unavailable, + "asset deposits are unavailable") +) + +// Server is the grpc server that serves the reservation service. +type Server struct { + looprpc.UnimplementedAssetDepositClientServer + + manager *Manager +} + +func NewServer(manager *Manager) *Server { + return &Server{ + manager: manager, + } +} + +// NewAssetDeposit is the rpc endpoint for loop clients to request a new asset +// deposit. +func (s *Server) NewAssetDeposit(ctx context.Context, + in *looprpc.NewAssetDepositRequest) (*looprpc.NewAssetDepositResponse, + error) { + + if s.manager == nil { + return nil, ErrAssetDepositsUnavailable + } + + assetIDBytes, err := hex.DecodeString(in.AssetId) + if err != nil { + return nil, status.Error(codes.InvalidArgument, + fmt.Sprintf("invalid asset ID encoding: %v", err)) + } + + var assetID asset.ID + if len(assetIDBytes) != len(assetID) { + return nil, fmt.Errorf("invalid asset ID lenght: expected "+ + "%v bytes, got %d", len(assetID), len(assetIDBytes)) + } + + copy(assetID[:], assetIDBytes) + + if in.Amount == 0 { + return nil, status.Error(codes.InvalidArgument, + "amount must be greater than zero") + } + + if in.CsvExpiry <= 0 { + return nil, status.Error(codes.InvalidArgument, + "CSV expiry must be greater than zero") + } + + depositInfo, err := s.manager.NewDeposit( + ctx, assetID, in.Amount, uint32(in.CsvExpiry), + ) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &looprpc.NewAssetDepositResponse{ + DepositId: depositInfo.ID, + }, nil +} + +// ListAssetDeposits is the rpc endpoint for loop clients to list their asset +// deposits. +func (s *Server) ListAssetDeposits(ctx context.Context, + in *looprpc.ListAssetDepositsRequest) ( + *looprpc.ListAssetDepositsResponse, error) { + + if s.manager == nil { + return nil, ErrAssetDepositsUnavailable + } + + if in.MinConfs < in.MaxConfs { + return nil, status.Error(codes.InvalidArgument, + "max_confs must be greater than or equal to min_confs") + } + + deposits, err := s.manager.ListDeposits(ctx, in.MinConfs, in.MaxConfs) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + filteredDeposits := make([]*looprpc.AssetDeposit, 0, len(deposits)) + for _, d := range deposits { + rpcDeposit := &looprpc.AssetDeposit{ + DepositId: d.ID, + CreatedAt: d.CreatedAt.Unix(), + AssetId: d.AssetID.String(), + Amount: d.Amount, + DepositAddr: d.Addr, + State: d.State.String(), + ConfirmationHeight: d.ConfirmationHeight, + Expiry: d.ConfirmationHeight + d.CsvExpiry, + SweepAddr: d.SweepAddr, + } + + if d.Outpoint != nil { + rpcDeposit.AnchorOutpoint = d.Outpoint.String() + } + + filteredDeposits = append(filteredDeposits, rpcDeposit) + } + + return &looprpc.ListAssetDepositsResponse{ + FilteredDeposits: filteredDeposits, + }, nil +} + +// RevealAssetDepositKey is the rpc endpoint for loop clients to reveal the +// asset deposit key for a specific asset deposit. +func (s *Server) RevealAssetDepositKey(ctx context.Context, + in *looprpc.RevealAssetDepositKeyRequest) ( + *looprpc.RevealAssetDepositKeyResponse, error) { + + if s.manager == nil { + return nil, ErrAssetDepositsUnavailable + } + + err := s.manager.RevealDepositKeys( + ctx, []string{in.DepositId}, + ) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &looprpc.RevealAssetDepositKeyResponse{}, nil +} + +// WithdrawAssetDeposits is the rpc endpoint for loop clients to withdraw their +// asset deposits. +func (s *Server) WithdrawAssetDeposits(ctx context.Context, + in *looprpc.WithdrawAssetDepositsRequest) ( + *looprpc.WithdrawAssetDepositsResponse, error) { + + if s.manager == nil { + return nil, ErrAssetDepositsUnavailable + } + + if len(in.DepositIds) == 0 { + return nil, status.Error(codes.InvalidArgument, + "at least one deposit id must be provided") + } + + err := s.manager.WithdrawDeposits(ctx, in.DepositIds) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &looprpc.WithdrawAssetDepositsResponse{}, nil +} + +// TestCoSignAssetDepositHTLC is the rpc endpoint for loop clients to test +// co-signing an asset deposit HTLC. +func (s *Server) TestCoSignAssetDepositHTLC(ctx context.Context, + in *looprpc.TestCoSignAssetDepositHTLCRequest) ( + *looprpc.TestCoSignAssetDepositHTLCResponse, error) { + + // Values for testing. + serverNonce := secNonceToPubNonce(tmpServerSecNonce) + preimage := lntypes.Preimage{1, 2, 3} + swapHash := preimage.Hash() + htlcExpiry := uint32(100) + + err := s.manager.CoSignHTLC( + ctx, in.DepositId, serverNonce, swapHash, htlcExpiry, + ) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &looprpc.TestCoSignAssetDepositHTLCResponse{}, nil +} diff --git a/assets/deposit/sql_store.go b/assets/deposit/sql_store.go new file mode 100644 index 000000000..8772a395d --- /dev/null +++ b/assets/deposit/sql_store.go @@ -0,0 +1,333 @@ +package deposit + +import ( + "context" + "database/sql" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/loop/loopdb" + "github.com/lightninglabs/loop/loopdb/sqlc" + "github.com/lightninglabs/taproot-assets/address" + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightningnetwork/lnd/clock" + "github.com/lightningnetwork/lnd/keychain" +) + +// Querier is a subset of the methods we need from the postgres.querier +// interface for the deposit store. +type Querier interface { + AddAssetDeposit(context.Context, sqlc.AddAssetDepositParams) error + + UpdateDepositState(ctx context.Context, + arg sqlc.UpdateDepositStateParams) error + + MarkDepositConfirmed(ctx context.Context, + arg sqlc.MarkDepositConfirmedParams) error + + GetAssetDeposits(ctx context.Context) ([]sqlc.GetAssetDepositsRow, + error) + + SetAssetDepositSweepAddr(ctx context.Context, + arg sqlc.SetAssetDepositSweepAddrParams) error + + GetActiveAssetDeposits(ctx context.Context) ( + []sqlc.GetActiveAssetDepositsRow, error) + + SetAssetDepositServerInternalKey(ctx context.Context, + arg sqlc.SetAssetDepositServerInternalKeyParams) error + + GetAssetDepositServerInternalKey(ctx context.Context, + depositID string) ([]byte, error) +} + +// DepositBaseDB is the interface that contains all the queries generated +// by sqlc for the deposit store. It also includes the ExecTx method for +// executing a function in the context of a database transaction. +type DepositBaseDB interface { + Querier + + // ExecTx allows for executing a function in the context of a database + // transaction. + ExecTx(ctx context.Context, txOptions loopdb.TxOptions, + txBody func(Querier) error) error +} + +// SQLStore is the high level SQL store for deposits. +type SQLStore struct { + db DepositBaseDB + + clock clock.Clock + addressParams address.ChainParams +} + +// NewSQLStore creates a new SQLStore. +func NewSQLStore(db DepositBaseDB, clock clock.Clock, + params *chaincfg.Params) *SQLStore { + + return &SQLStore{ + db: db, + clock: clock, + addressParams: address.ParamsForChain(params.Name), + } +} + +// AddAssetDeposit adds a new asset deposit to the database. +func (s *SQLStore) AddAssetDeposit(ctx context.Context, d *Deposit) error { + txOptions := loopdb.NewSqlWriteOpts() + + createdAt := d.CreatedAt.UTC() + clientScriptPubKey := d.FunderScriptKey.SerializeCompressed() + clientInternalPubKey := d.FunderInternalKey.SerializeCompressed() + serverScriptPubKey := d.CoSignerScriptKey.SerializeCompressed() + serverInternalPubKey := d.CoSignerInternalKey.SerializeCompressed() + + return s.db.ExecTx(ctx, txOptions, func(tx Querier) error { + err := tx.AddAssetDeposit(ctx, sqlc.AddAssetDepositParams{ + DepositID: d.ID, + CreatedAt: createdAt, + AssetID: d.AssetID[:], + Amount: int64(d.Amount), + ClientScriptPubkey: clientScriptPubKey, + ClientInternalPubkey: clientInternalPubKey, + ServerScriptPubkey: serverScriptPubKey, + ServerInternalPubkey: serverInternalPubKey, + ClientKeyFamily: int32(d.KeyLocator.Family), + ClientKeyIndex: int32(d.KeyLocator.Index), + Expiry: int32(d.CsvExpiry), + Addr: d.Addr, + ProtocolVersion: int32(d.Version), + }) + if err != nil { + return err + } + + return tx.UpdateDepositState(ctx, sqlc.UpdateDepositStateParams{ + DepositID: d.ID, + UpdateState: int32(StateInitiated), + UpdateTimestamp: createdAt, + }) + }) +} + +// UpdateDeposit updates the deposit state and extends the depsoit update log +// the SQL store. +func (s *SQLStore) UpdateDeposit(ctx context.Context, d *Deposit) error { + txOptions := loopdb.NewSqlWriteOpts() + + return s.db.ExecTx(ctx, txOptions, func(tx Querier) error { + switch d.State { + case StateConfirmed: + err := tx.MarkDepositConfirmed( + ctx, sqlc.MarkDepositConfirmedParams{ + DepositID: d.ID, + ConfirmationHeight: sql.NullInt32{ + Int32: int32( + d.ConfirmationHeight, + ), + Valid: true, + }, + Outpoint: sql.NullString{ + String: d.Outpoint.String(), + Valid: true, + }, + PkScript: d.PkScript, + }, + ) + if err != nil { + return err + } + + case StateExpired: + fallthrough + case StateWithdrawn: + err := tx.SetAssetDepositSweepAddr( + ctx, sqlc.SetAssetDepositSweepAddrParams{ + DepositID: d.ID, + SweepAddr: sql.NullString{ + String: d.SweepAddr, + Valid: true, + }, + }, + ) + if err != nil { + return err + } + } + + return tx.UpdateDepositState( + ctx, sqlc.UpdateDepositStateParams{ + DepositID: d.ID, + UpdateState: int32(d.State), + UpdateTimestamp: s.clock.Now().UTC(), + }, + ) + }) +} + +func (s *SQLStore) GetAllDeposits(ctx context.Context) ([]Deposit, error) { + sqlDeposits, err := s.db.GetAssetDeposits(ctx) + if err != nil { + return nil, err + } + + deposits := make([]Deposit, 0, len(sqlDeposits)) + for _, sqlDeposit := range sqlDeposits { + deposit, err := sqlcDepositToDeposit( + sqlDeposit, &s.addressParams, + ) + if err != nil { + return nil, err + } + + deposits = append(deposits, deposit) + } + + return deposits, nil +} + +func sqlcDepositToDeposit(sqlDeposit sqlc.GetAssetDepositsRow, + addressParams *address.ChainParams) (Deposit, error) { + + clientScriptPubKey, err := btcec.ParsePubKey( + sqlDeposit.ClientScriptPubkey, + ) + if err != nil { + return Deposit{}, err + } + + serverScriptPubKey, err := btcec.ParsePubKey( + sqlDeposit.ServerScriptPubkey, + ) + if err != nil { + return Deposit{}, err + } + + clientInteralPubKey, err := btcec.ParsePubKey( + sqlDeposit.ClientInternalPubkey, + ) + if err != nil { + return Deposit{}, err + } + + serverInternalPubKey, err := btcec.ParsePubKey( + sqlDeposit.ServerInternalPubkey, + ) + if err != nil { + return Deposit{}, err + } + + clientKeyLocator := keychain.KeyLocator{ + Family: keychain.KeyFamily( + sqlDeposit.ClientKeyFamily, + ), + Index: uint32(sqlDeposit.ClientKeyIndex), + } + + if len(sqlDeposit.AssetID) != len(asset.ID{}) { + return Deposit{}, fmt.Errorf("malformed asset ID for deposit: "+ + "%v", sqlDeposit.DepositID) + } + + depositInfo := &DepositInfo{ + ID: sqlDeposit.DepositID, + Version: AssetDepositProtocolVersion( + sqlDeposit.ProtocolVersion, + ), + CreatedAt: sqlDeposit.CreatedAt.Local(), + Amount: uint64(sqlDeposit.Amount), + Addr: sqlDeposit.Addr, + State: State(sqlDeposit.UpdateState), + } + + if sqlDeposit.ConfirmationHeight.Valid { + depositInfo.ConfirmationHeight = uint32( + sqlDeposit.ConfirmationHeight.Int32, + ) + } + + if sqlDeposit.Outpoint.Valid { + outpoint, err := wire.NewOutPointFromString( + sqlDeposit.Outpoint.String, + ) + if err != nil { + return Deposit{}, err + } + + depositInfo.Outpoint = outpoint + } + + if sqlDeposit.SweepAddr.Valid { + depositInfo.SweepAddr = sqlDeposit.SweepAddr.String + } + + kit, err := NewKit( + clientScriptPubKey, clientInteralPubKey, serverScriptPubKey, + serverInternalPubKey, clientKeyLocator, + asset.ID(sqlDeposit.AssetID), uint32(sqlDeposit.Expiry), + addressParams, + ) + if err != nil { + return Deposit{}, err + } + + return Deposit{ + Kit: kit, + DepositInfo: depositInfo, + }, nil +} + +// GetActiveDeposits returns all active deposits from the database. Active +// deposits are those that have not yet been spent or swept. +func (s *SQLStore) GetActiveDeposits(ctx context.Context) ([]Deposit, error) { + sqlDeposits, err := s.db.GetActiveAssetDeposits(ctx) + if err != nil { + return nil, err + } + + deposits := make([]Deposit, 0, len(sqlDeposits)) + for _, sqlDeposit := range sqlDeposits { + deposit, err := sqlcDepositToDeposit( + sqlc.GetAssetDepositsRow(sqlDeposit), + &s.addressParams, + ) + if err != nil { + return nil, err + } + + deposits = append(deposits, deposit) + } + + return deposits, nil +} + +// SetAssetDepositServerKey sets the server's internal key for the give asset +// deposit. +func (s *SQLStore) SetAssetDepositServerKey(ctx context.Context, + depositID string, key *btcec.PrivateKey) error { + + return s.db.SetAssetDepositServerInternalKey( + ctx, sqlc.SetAssetDepositServerInternalKeyParams{ + DepositID: depositID, + ServerInternalKey: key.Serialize(), + }, + ) +} + +func (s *SQLStore) GetAssetDepositServerKey(ctx context.Context, + depositID string) (*btcec.PrivateKey, error) { + + keyBytes, err := s.db.GetAssetDepositServerInternalKey(ctx, depositID) + if err != nil { + return nil, err + } + + key, _ := btcec.PrivKeyFromBytes(keyBytes) + if err != nil { + return nil, err + } + + return key, nil +} diff --git a/assets/deposit/sweeper.go b/assets/deposit/sweeper.go new file mode 100644 index 000000000..4d8b33607 --- /dev/null +++ b/assets/deposit/sweeper.go @@ -0,0 +1,448 @@ +package deposit + +import ( + "bytes" + "context" + "fmt" + "strings" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/wallet" + "github.com/btcsuite/btcwallet/wtxmgr" + "github.com/lightninglabs/lndclient" + "github.com/lightninglabs/loop/assets" + "github.com/lightninglabs/loop/assets/htlc" + "github.com/lightninglabs/loop/utils" + "github.com/lightninglabs/taproot-assets/address" + "github.com/lightninglabs/taproot-assets/proof" + "github.com/lightninglabs/taproot-assets/tappsbt" + "github.com/lightninglabs/taproot-assets/taprpc" + "github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" +) + +// Sweeper is a higher level type that provides methods to sweep asset deposits. +type Sweeper struct { + tapdClient *assets.TapdClient + walletKit lndclient.WalletKitClient + signer lndclient.SignerClient + + addressParams address.ChainParams +} + +// NewSweeper creates a new Sweeper instance. +func NewSweeper(tapdClient *assets.TapdClient, + walletKit lndclient.WalletKitClient, signer lndclient.SignerClient, + addressParams address.ChainParams) *Sweeper { + + return &Sweeper{ + tapdClient: tapdClient, + walletKit: walletKit, + signer: signer, + addressParams: addressParams, + } +} + +// PublishDepositSweepMuSig2 publishes a deposit sweep using the MuSig2 keyspend +// path. +func (s *Sweeper) PublishDepositSweepMuSig2(ctx context.Context, deposit *Kit, + funder bool, depositProof *proof.Proof, + otherInternalKey *btcec.PrivateKey, sweepAddr *address.Tap, + feeRate chainfee.SatPerVByte, lockID wtxmgr.LockID, + lockDuration time.Duration) (*taprpc.SendAssetResponse, error) { + + // Verify that the proof is valid for the deposit and get the root hash + // which we will be using as our taproot tweak. + rootHash, err := deposit.VerifyProof(depositProof) + if err != nil { + log.Errorf("failed to verify deposit proof: %v", err) + + return nil, err + } + + // Now we can create the sweep vpacket which is simply sweeping the + // asset on the OP_TRUE output to the timeout sweep address. + sweepVpkt, err := assets.CreateOpTrueSweepVpkt( + ctx, []*proof.Proof{depositProof}, sweepAddr, &s.addressParams, + ) + if err != nil { + return nil, err + } + + // Gather the list of leased UTXOs that are used for the deposit sweep. + // This is needed to ensure that the UTXOs are correctly reused if we + // re-publish the deposit sweep. + leases, err := s.walletKit.ListLeases(ctx) + if err != nil { + return nil, err + } + + var leasedUtxos []lndclient.LeaseDescriptor + for _, lease := range leases { + if lease.LockID == lockID { + leasedUtxos = append(leasedUtxos, lease) + } + } + + // By committing the virtual transaction to the BTC template we created, + // the underlying lnd node will fund the BTC level transaction with an + // input to pay for the fees (and it will also add a change output). + sweepBtcPkt, activeAssets, passiveAssets, commitResp, err := + s.tapdClient.PrepareAndCommitVirtualPsbts( + ctx, sweepVpkt, feeRate, nil, s.addressParams.Params, + leasedUtxos, &lockID, lockDuration, + ) + if err != nil { + return nil, err + } + + prevOutFetcher := wallet.PsbtPrevOutputFetcher(sweepBtcPkt) + sigHash, err := getSigHash(sweepBtcPkt.UnsignedTx, 0, prevOutFetcher) + if err != nil { + return nil, err + } + + tweaks := &input.MuSig2Tweaks{ + TaprootTweak: rootHash[:], + } + + pubKey := deposit.FunderScriptKey + otherInternalPubKey := deposit.CoSignerInternalKey + if !funder { + pubKey = deposit.CoSignerScriptKey + otherInternalPubKey = deposit.FunderInternalKey + } + + internalPubKey, internalKey, err := DeriveSharedDepositKey( + ctx, s.signer, pubKey, + ) + + finalSig, err := utils.MuSig2Sign( + input.MuSig2Version100RC2, + []*btcec.PrivateKey{internalKey, otherInternalKey}, + []*btcec.PublicKey{internalPubKey, otherInternalPubKey}, + tweaks, sigHash, + ) + if err != nil { + return nil, err + } + + // Make sure that the signature is valid for the tx sighash and deposit + // internal key. + schnorrSig, err := schnorr.ParseSignature(finalSig) + if err != nil { + return nil, err + } + + // Calculate the final, tweaked MuSig2 output key. + taprootOutputKey := txscript.ComputeTaprootOutputKey( + deposit.MuSig2Key.PreTweakedKey, rootHash[:], + ) + + // Make sure we always return the parity stripped key. + taprootOutputKey, _ = schnorr.ParsePubKey(schnorr.SerializePubKey( + taprootOutputKey, + )) + + // Finally, verify that the signature is valid for the sighash and + // tweaked MuSig2 output key. + if !schnorrSig.Verify(sigHash[:], taprootOutputKey) { + return nil, fmt.Errorf("invalid signature") + } + + // Create the witness and add it to the sweep packet. + var buf bytes.Buffer + err = psbt.WriteTxWitness(&buf, wire.TxWitness{finalSig}) + if err != nil { + return nil, err + } + + sweepBtcPkt.Inputs[0].FinalScriptWitness = buf.Bytes() + + // Sign and finalize the sweep packet. + signedBtcPacket, err := s.walletKit.SignPsbt(ctx, sweepBtcPkt) + if err != nil { + return nil, err + } + + finalizedBtcPacket, _, err := s.walletKit.FinalizePsbt( + ctx, signedBtcPacket, "", + ) + if err != nil { + return nil, err + } + + // Finally publish the sweep and log the transfer. + skipBroadcast := false + sendAssetResp, err := s.tapdClient.LogAndPublish( + ctx, finalizedBtcPacket, activeAssets, passiveAssets, + commitResp, skipBroadcast, + ) + + return sendAssetResp, err +} + +// PublishDepositTimeoutSweep publishes a deposit timeout sweep using the +// timeout script spend path. +func (s *Sweeper) PublishDepositTimeoutSweep(ctx context.Context, deposit *Kit, + depositProof *proof.Proof, sweepAddr *address.Tap, + feeRate chainfee.SatPerVByte, lockID wtxmgr.LockID, + lockDuration time.Duration) (*taprpc.SendAssetResponse, error) { + + // Create the sweep vpacket which is simply sweeping the asset on the + // OP_TRUE output to the timeout sweep address. + sweepVpkt, err := assets.CreateOpTrueSweepVpkt( + ctx, []*proof.Proof{depositProof}, sweepAddr, + &s.addressParams, + ) + if err != nil { + log.Errorf("Unable to create timeout sweep vpkt: %v", err) + + return nil, err + } + + // Gather the list of leased UTXOs that are used for the deposit sweep. + // This is needed to ensure that the UTXOs are correctly reused if we + // re-publish the deposit sweep. + leases, err := s.walletKit.ListLeases(ctx) + if err != nil { + log.Errorf("Unable to list leases: %v", err) + + return nil, err + } + + var leasedUtxos []lndclient.LeaseDescriptor + for _, lease := range leases { + if lease.LockID == lockID { + leasedUtxos = append(leasedUtxos, lease) + } + } + + // By committing the virtual transaction to the BTC template we created, + // the underlying lnd node will fund the BTC level transaction with an + // input to pay for the fees (and it will also add a change output). + timeoutSweepBtcPkt, activeAssets, passiveAssets, commitResp, err := + s.tapdClient.PrepareAndCommitVirtualPsbts( + ctx, sweepVpkt, feeRate, nil, + s.addressParams.Params, leasedUtxos, + &lockID, lockDuration, + ) + if err != nil { + log.Errorf("Unable to prepare and commit virtual psbt: %v", + err) + } + + // Create the witness for the timeout sweep. + witness, err := deposit.CreateTimeoutWitness( + ctx, s.signer, depositProof, timeoutSweepBtcPkt, + ) + if err != nil { + log.Errorf("Unable to create timeout witness: %v", err) + + return nil, err + } + + // Now add the witness to the sweep packet. + var buf bytes.Buffer + err = psbt.WriteTxWitness(&buf, witness) + if err != nil { + log.Errorf("Unable to write witness to buffer: %v", err) + + return nil, err + } + + timeoutSweepBtcPkt.Inputs[0].SighashType = txscript.SigHashDefault + timeoutSweepBtcPkt.Inputs[0].FinalScriptWitness = buf.Bytes() + + // Sign and finalize the sweep packet. + signedBtcPacket, err := s.walletKit.SignPsbt(ctx, timeoutSweepBtcPkt) + if err != nil { + log.Errorf("Unable to sign timeout sweep packet: %v", err) + + return nil, err + } + + finalizedBtcPacket, _, err := s.walletKit.FinalizePsbt( + ctx, signedBtcPacket, "", + ) + if err != nil { + log.Errorf("Unable to finalize timeout sweep packet: %v", err) + + return nil, err + } + + anchorTxHash := depositProof.AnchorTx.TxHash() + depositOutIdx := depositProof.InclusionProof.OutputIndex + + // Register the deposit transfer. This essentially materializes an asset + // "out of thin air" to ensure that LogAndPublish succeeds and the asset + // balance will be updated correctly. + depositScriptKey := depositProof.Asset.ScriptKey.PubKey + _, err = s.tapdClient.RegisterTransfer( + ctx, &taprpc.RegisterTransferRequest{ + AssetId: deposit.AssetID[:], + GroupKey: nil, + ScriptKey: depositScriptKey.SerializeCompressed(), + Outpoint: &taprpc.OutPoint{ + Txid: anchorTxHash[:], + OutputIndex: depositOutIdx, + }, + }, + ) + if err != nil { + if !strings.Contains(err.Error(), "proof already exists") { + log.Errorf("Unable to register deposit transfer: %v", + err) + + return nil, err + } + } + + // Publish the timeout sweep and log the transfer. + sendAssetResp, err := s.tapdClient.LogAndPublish( + ctx, finalizedBtcPacket, activeAssets, passiveAssets, + commitResp, false, + ) + if err != nil { + log.Errorf("Failed to publish timeout sweep: %v", err) + + return nil, err + } + + return sendAssetResp, nil +} + +// getSigHash calculates the signature hash for the given transaction. +func getSigHash(tx *wire.MsgTx, idx int, + prevOutFetcher txscript.PrevOutputFetcher) ([32]byte, error) { + + var sigHash [32]byte + + sigHashes := txscript.NewTxSigHashes(tx, prevOutFetcher) + taprootSigHash, err := txscript.CalcTaprootSignatureHash( + sigHashes, txscript.SigHashDefault, tx, idx, prevOutFetcher, + ) + if err != nil { + return sigHash, err + } + + copy(sigHash[:], taprootSigHash) + + return sigHash, nil +} + +// GetHTLC creates a new zero-fee HTLC packet to be able to partially sign it +// and send it to the server for further processing. +// +// TODO(bhandras): add support for spending multiple deposits into the HTLC. +func (s *Sweeper) GetHTLC(ctx context.Context, deposit *Kit, + depositProof *proof.Proof, amount uint64, hash lntypes.Hash, + csvExpiry uint32) (*htlc.SwapKit, *psbt.Packet, []*tappsbt.VPacket, + []*tappsbt.VPacket, *assetwalletrpc.CommitVirtualPsbtsResponse, error) { + + // Genearate the HTLC address that will be used to sweep the deposit to + // in case the client is uncooperative. + rpcHtlcAddr, swapKit, err := deposit.NewHtlcAddr( + ctx, s.tapdClient, amount, hash, csvExpiry, + ) + if err != nil { + return nil, nil, nil, nil, nil, fmt.Errorf("unable to create "+ + "htlc addr: %v", err) + } + + htlcAddr, err := address.DecodeAddress( + rpcHtlcAddr.Encoded, &s.addressParams, + ) + if err != nil { + return nil, nil, nil, nil, nil, err + } + + // Now we can create the sweep vpacket that'd sweep the deposited + // assets to the HTLC output. + depositSpendVpkt, err := assets.CreateOpTrueSweepVpkt( + ctx, []*proof.Proof{depositProof}, htlcAddr, &s.addressParams, + ) + if err != nil { + return nil, nil, nil, nil, nil, fmt.Errorf("unable to create "+ + "deposit spend vpacket: %v", err) + } + + // By committing the virtual transaction to the BTC template we + // created, our lnd node will fund the BTC level transaction with an + // input to pay for the fees. We'll further add a change output to the + // transaction that will be generated using the above key descriptor. + feeRate := chainfee.SatPerVByte(0) + + // Use an empty lock ID, as we don't need to lock any UTXOs for this + // operation. + lockID := wtxmgr.LockID{} + + htlcBtcPkt, activeAssets, passiveAssets, commitResp, err := + s.tapdClient.PrepareAndCommitVirtualPsbts( + ctx, depositSpendVpkt, feeRate, nil, + s.addressParams.Params, nil, &lockID, + time.Duration(0), + ) + if err != nil { + return nil, nil, nil, nil, nil, fmt.Errorf("deposit spend "+ + "HTLC prepare and commit failed: %v", err) + } + + htlcBtcPkt.UnsignedTx.Version = 3 + + return swapKit, htlcBtcPkt, activeAssets, passiveAssets, commitResp, nil +} + +// PartialSignMuSig2 is used to partially sign a message hash with the deposit's +// keys. +func (s *Sweeper) PartialSignMuSig2(ctx context.Context, d *Kit, + anchorRootHash []byte, cosignerNonce [musig2.PubNonceSize]byte, + message [32]byte) ([musig2.PubNonceSize]byte, []byte, error) { + + signers := [][]byte{ + d.FunderInternalKey.SerializeCompressed(), + d.CoSignerInternalKey.SerializeCompressed(), + } + + session, err := s.signer.MuSig2CreateSession( + ctx, input.MuSig2Version100RC2, + &keychain.KeyLocator{ + Family: d.KeyLocator.Family, + Index: d.KeyLocator.Index, + }, signers, lndclient.MuSig2TaprootTweakOpt( + anchorRootHash, false, + ), + ) + if err != nil { + return [musig2.PubNonceSize]byte{}, nil, err + } + + _, err = s.signer.MuSig2RegisterNonces( + ctx, session.SessionID, + [][musig2.PubNonceSize]byte{cosignerNonce}, + ) + if err != nil { + return [musig2.PubNonceSize]byte{}, nil, err + } + + clientPartialSig, err := s.signer.MuSig2Sign( + ctx, session.SessionID, message, true, + ) + if err != nil { + return [musig2.PubNonceSize]byte{}, nil, err + } + + fmt.Printf("!!! client partial sig: %x\n", clientPartialSig) + fmt.Printf("!!! client nonce: %x\n", session.PublicNonce) + + return session.PublicNonce, clientPartialSig, nil +} diff --git a/assets/deposit/testing.go b/assets/deposit/testing.go new file mode 100644 index 000000000..d6e9661a0 --- /dev/null +++ b/assets/deposit/testing.go @@ -0,0 +1,57 @@ +package deposit + +import ( + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" +) + +var ( + // tmpServerSecNonce is a secret nonce used for testing. + // TODO(bhandras): remove once package spending works e2e. + tmpServerSecNonce = [musig2.SecNonceSize]byte{ + // First 32 bytes: scalar k1 + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, + 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, + 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, 0x01, + + // Second 32 bytes: scalar k2 + 0x02, 0x13, 0x24, 0x35, 0x46, 0x57, 0x68, 0x79, + 0x8a, 0x9b, 0xac, 0xbd, 0xce, 0xdf, 0xf1, 0x02, + 0x14, 0x26, 0x38, 0x4a, 0x5c, 0x6e, 0x70, 0x82, + 0x94, 0xa6, 0xb8, 0xca, 0xdc, 0xee, 0xf1, 0x03, + } +) + +// secNonceToPubNonce takes our two secret nonces, and produces their two +// corresponding EC points, serialized in compressed format. +func secNonceToPubNonce( + secNonce [musig2.SecNonceSize]byte) [musig2.PubNonceSize]byte { + + var k1Mod, k2Mod btcec.ModNScalar + k1Mod.SetByteSlice(secNonce[:btcec.PrivKeyBytesLen]) + k2Mod.SetByteSlice(secNonce[btcec.PrivKeyBytesLen:]) + + var r1, r2 btcec.JacobianPoint + btcec.ScalarBaseMultNonConst(&k1Mod, &r1) + btcec.ScalarBaseMultNonConst(&k2Mod, &r2) + + // Next, we'll convert the key in jacobian format to a normal public + // key expressed in affine coordinates. + r1.ToAffine() + r2.ToAffine() + r1Pub := btcec.NewPublicKey(&r1.X, &r1.Y) + r2Pub := btcec.NewPublicKey(&r2.X, &r2.Y) + + var pubNonce [musig2.PubNonceSize]byte + + // The public nonces are serialized as: R1 || R2, where both keys are + // serialized in compressed format. + copy(pubNonce[:], r1Pub.SerializeCompressed()) + copy( + pubNonce[btcec.PubKeyBytesLenCompressed:], + r2Pub.SerializeCompressed(), + ) + + return pubNonce +} diff --git a/assets/htlc/script.go b/assets/htlc/script.go new file mode 100644 index 000000000..9ce7066ba --- /dev/null +++ b/assets/htlc/script.go @@ -0,0 +1,96 @@ +package htlc + +import ( + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/txscript" + "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lntypes" +) + +// GenSuccessPathScript constructs a script for the success path of the HTLC +// payment. Optionally includes a CHECKSEQUENCEVERIFY (CSV) of 1 if `csv` is +// true, to prevent potential pinning attacks when the HTLC is not part of a +// package relay. +func GenSuccessPathScript(receiverHtlcKey *btcec.PublicKey, + swapHash lntypes.Hash, csv bool) ([]byte, error) { + + builder := txscript.NewScriptBuilder() + + builder.AddData(schnorr.SerializePubKey(receiverHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + builder.AddOp(txscript.OP_SIZE) + builder.AddInt64(32) + builder.AddOp(txscript.OP_EQUALVERIFY) + builder.AddOp(txscript.OP_HASH160) + builder.AddData(input.Ripemd160H(swapHash[:])) + builder.AddOp(txscript.OP_EQUALVERIFY) + + if csv { + builder.AddInt64(1) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + } + + return builder.Script() +} + +// GenTimeoutPathScript constructs an HtlcScript for the timeout payment path. +func GenTimeoutPathScript(senderHtlcKey *btcec.PublicKey, csvExpiry int64) ( + []byte, error) { + + builder := txscript.NewScriptBuilder() + builder.AddData(schnorr.SerializePubKey(senderHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + builder.AddInt64(csvExpiry) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + return builder.Script() +} + +// GetOpTrueScript returns a script that always evaluates to true. +func GetOpTrueScript() ([]byte, error) { + return txscript.NewScriptBuilder().AddOp(txscript.OP_TRUE).Script() +} + +// CreateOpTrueLeaf creates a taproot leaf that always evaluates to true. +func CreateOpTrueLeaf() (asset.ScriptKey, txscript.TapLeaf, + *txscript.IndexedTapScriptTree, *txscript.ControlBlock, error) { + + // Create the taproot OP_TRUE script. + tapScript, err := GetOpTrueScript() + if err != nil { + return asset.ScriptKey{}, txscript.TapLeaf{}, nil, nil, err + } + + tapLeaf := txscript.NewBaseTapLeaf(tapScript) + tree := txscript.AssembleTaprootScriptTree(tapLeaf) + rootHash := tree.RootNode.TapHash() + tapKey := txscript.ComputeTaprootOutputKey( + asset.NUMSPubKey, rootHash[:], + ) + + merkleRootHash := tree.RootNode.TapHash() + + controlBlock := &txscript.ControlBlock{ + LeafVersion: txscript.BaseLeafVersion, + InternalKey: asset.NUMSPubKey, + } + tapScriptKey := asset.ScriptKey{ + PubKey: tapKey, + TweakedScriptKey: &asset.TweakedScriptKey{ + RawKey: keychain.KeyDescriptor{ + PubKey: asset.NUMSPubKey, + }, + Tweak: merkleRootHash[:], + }, + } + if tapKey.SerializeCompressed()[0] == + secp256k1.PubKeyFormatCompressedOdd { + + controlBlock.OutputKeyYIsOdd = true + } + + return tapScriptKey, tapLeaf, tree, controlBlock, nil +} diff --git a/assets/htlc/swapkit.go b/assets/htlc/swapkit.go new file mode 100644 index 000000000..0724d7ff9 --- /dev/null +++ b/assets/htlc/swapkit.go @@ -0,0 +1,465 @@ +package htlc + +import ( + "context" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/lightninglabs/lndclient" + "github.com/lightninglabs/taproot-assets/address" + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/commitment" + "github.com/lightninglabs/taproot-assets/proof" + "github.com/lightninglabs/taproot-assets/tappsbt" + "github.com/lightninglabs/taproot-assets/tapscript" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lntypes" +) + +// SwapKit holds information needed to facilitate an on-chain asset to offchain +// bitcoin atomic swap. The keys within the struct are the public keys of the +// sender and receiver that will be used to create the on-chain HTLC. +type SwapKit struct { + // SenderPubKey is the public key of the sender for the joint key + // that will be used to create the HTLC. + SenderPubKey *btcec.PublicKey + + // ReceiverPubKey is the public key of the receiver that will be used to + // create the HTLC. + ReceiverPubKey *btcec.PublicKey + + // AssetID is the identifier of the asset that will be swapped. + AssetID []byte + + // Amount is the amount of the asset that will be swapped. Note that + // we use btcutil.Amount here for simplicity, but the actual amount + // is in the asset's native unit. + Amount btcutil.Amount + + // SwapHash is the hash of the preimage in the swap HTLC. + SwapHash lntypes.Hash + + // CsvExpiry is the relative timelock in blocks for the swap. + CsvExpiry uint32 + + // AddressParams is the chain parameters of the chain the deposit is + // being created on. + AddressParams *address.ChainParams + + // CheckCSV indicates whether the success path script should include a + // CHECKSEQUENCEVERIFY check. This is used to prevent potential pinning + // attacks when the HTLC is not part of a package relay. + CheckCSV bool +} + +// GetSuccessScript returns the success path script of the swap HTLC. +func (s *SwapKit) GetSuccessScript() ([]byte, error) { + return GenSuccessPathScript(s.ReceiverPubKey, s.SwapHash, s.CheckCSV) +} + +// GetTimeoutScript returns the timeout path script of the swap HTLC. +func (s *SwapKit) GetTimeoutScript() ([]byte, error) { + return GenTimeoutPathScript(s.SenderPubKey, int64(s.CsvExpiry)) +} + +// GetAggregateKey returns the aggregate MuSig2 key used in the swap HTLC. +func (s *SwapKit) GetAggregateKey() (*btcec.PublicKey, error) { + aggregateKey, err := input.MuSig2CombineKeys( + input.MuSig2Version100RC2, + []*btcec.PublicKey{ + s.SenderPubKey, s.ReceiverPubKey, + }, + true, + &input.MuSig2Tweaks{}, + ) + if err != nil { + return nil, err + } + + return aggregateKey.PreTweakedKey, nil +} + +// GetTimeOutLeaf returns the timeout leaf of the swap. +func (s *SwapKit) GetTimeOutLeaf() (txscript.TapLeaf, error) { + timeoutScript, err := s.GetTimeoutScript() + if err != nil { + return txscript.TapLeaf{}, err + } + + timeoutLeaf := txscript.NewBaseTapLeaf(timeoutScript) + + return timeoutLeaf, nil +} + +// GetSuccessLeaf returns the success leaf of the swap. +func (s *SwapKit) GetSuccessLeaf() (txscript.TapLeaf, error) { + successScript, err := s.GetSuccessScript() + if err != nil { + return txscript.TapLeaf{}, err + } + + successLeaf := txscript.NewBaseTapLeaf(successScript) + + return successLeaf, nil +} + +// GetSiblingPreimage returns the sibling preimage of the HTLC bitcoin top level +// output. +func (s *SwapKit) GetSiblingPreimage() (commitment.TapscriptPreimage, error) { + timeOutLeaf, err := s.GetTimeOutLeaf() + if err != nil { + return commitment.TapscriptPreimage{}, err + } + + successLeaf, err := s.GetSuccessLeaf() + if err != nil { + return commitment.TapscriptPreimage{}, err + } + + branch := txscript.NewTapBranch(timeOutLeaf, successLeaf) + + siblingPreimage := commitment.NewPreimageFromBranch(branch) + + return siblingPreimage, nil +} + +// CreateHtlcVpkt creates the vpacket for the HTLC. +func (s *SwapKit) CreateHtlcVpkt() (*tappsbt.VPacket, error) { + assetId := asset.ID{} + copy(assetId[:], s.AssetID) + + btcInternalKey, err := s.GetAggregateKey() + if err != nil { + return nil, err + } + + siblingPreimage, err := s.GetSiblingPreimage() + if err != nil { + return nil, err + } + + tapScriptKey, _, _, _, err := CreateOpTrueLeaf() + if err != nil { + return nil, err + } + + pkt := &tappsbt.VPacket{ + Inputs: []*tappsbt.VInput{{ + PrevID: asset.PrevID{ + ID: assetId, + }, + }}, + Outputs: make([]*tappsbt.VOutput, 0, 2), + ChainParams: s.AddressParams, + Version: tappsbt.V1, + } + pkt.Outputs = append(pkt.Outputs, &tappsbt.VOutput{ + Amount: 0, + Type: tappsbt.TypeSplitRoot, + AnchorOutputIndex: 0, + ScriptKey: asset.NUMSScriptKey, + }) + pkt.Outputs = append(pkt.Outputs, &tappsbt.VOutput{ + AssetVersion: asset.V1, + Amount: uint64(s.Amount), + AnchorOutputIndex: 1, + ScriptKey: asset.NewScriptKey( + tapScriptKey.PubKey, + ), + AnchorOutputInternalKey: btcInternalKey, + AnchorOutputTapscriptSibling: &siblingPreimage, + }) + + return pkt, nil +} + +// GenTimeoutBtcControlBlock generates the control block for the timeout path of +// the swap. +func (s *SwapKit) GenTimeoutBtcControlBlock(taprootAssetRoot []byte) ( + *txscript.ControlBlock, error) { + + internalKey, err := s.GetAggregateKey() + if err != nil { + return nil, err + } + + successLeaf, err := s.GetSuccessLeaf() + if err != nil { + return nil, err + } + + successLeafHash := successLeaf.TapHash() + + btcControlBlock := &txscript.ControlBlock{ + InternalKey: internalKey, + LeafVersion: txscript.BaseLeafVersion, + InclusionProof: append( + successLeafHash[:], taprootAssetRoot..., + ), + } + + timeoutPathScript, err := s.GetTimeoutScript() + if err != nil { + return nil, err + } + + rootHash := btcControlBlock.RootHash(timeoutPathScript) + tapKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash) + if tapKey.SerializeCompressed()[0] == + secp256k1.PubKeyFormatCompressedOdd { + + btcControlBlock.OutputKeyYIsOdd = true + } + + return btcControlBlock, nil +} + +// GenSuccessBtcControlBlock generates the control block for the timeout path of +// the swap. +func (s *SwapKit) GenSuccessBtcControlBlock(taprootAssetRoot []byte) ( + *txscript.ControlBlock, error) { + + internalKey, err := s.GetAggregateKey() + if err != nil { + return nil, err + } + + timeOutLeaf, err := s.GetTimeOutLeaf() + if err != nil { + return nil, err + } + + timeOutLeafHash := timeOutLeaf.TapHash() + + btcControlBlock := &txscript.ControlBlock{ + InternalKey: internalKey, + LeafVersion: txscript.BaseLeafVersion, + InclusionProof: append( + timeOutLeafHash[:], taprootAssetRoot..., + ), + } + + successPathScript, err := s.GetSuccessScript() + if err != nil { + return nil, err + } + + rootHash := btcControlBlock.RootHash(successPathScript) + tapKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash) + if tapKey.SerializeCompressed()[0] == + secp256k1.PubKeyFormatCompressedOdd { + + btcControlBlock.OutputKeyYIsOdd = true + } + + return btcControlBlock, nil +} + +// GenTaprootAssetRootFromProof generates the taproot asset root from the proof +// of the swap. +func GenTaprootAssetRootFromProof(proof *proof.Proof) ([]byte, error) { + assetCopy := proof.Asset.CopySpendTemplate() + + version := commitment.TapCommitmentV2 + assetCommitment, err := commitment.FromAssets( + &version, assetCopy, + ) + if err != nil { + return nil, err + } + + assetCommitment, err = commitment.TrimSplitWitnesses( + &version, assetCommitment, + ) + if err != nil { + return nil, err + } + + taprootAssetRoot := assetCommitment.TapscriptRoot(nil) + + return taprootAssetRoot[:], nil +} + +// GetPkScriptFromAsset returns the toplevel bitcoin script with the given +// asset. +func (s *SwapKit) GetPkScriptFromAsset(asset *asset.Asset) ([]byte, error) { + assetCopy := asset.CopySpendTemplate() + + version := commitment.TapCommitmentV2 + assetCommitment, err := commitment.FromAssets( + &version, assetCopy, + ) + if err != nil { + return nil, err + } + + assetCommitment, err = commitment.TrimSplitWitnesses( + &version, assetCommitment, + ) + if err != nil { + return nil, err + } + + siblingPreimage, err := s.GetSiblingPreimage() + if err != nil { + return nil, err + } + + siblingHash, err := siblingPreimage.TapHash() + if err != nil { + return nil, err + } + + btcInternalKey, err := s.GetAggregateKey() + if err != nil { + return nil, err + } + + return tapscript.PayToAddrScript( + *btcInternalKey, siblingHash, *assetCommitment, + ) +} + +// CreatePreimageWitness creates a preimage witness for the swap. +func (s *SwapKit) CreatePreimageWitness(ctx context.Context, + signer lndclient.SignerClient, htlcProof *proof.Proof, + sweepBtcPacket *psbt.Packet, keyLocator keychain.KeyLocator, + preimage lntypes.Preimage) (wire.TxWitness, error) { + + assetTxOut := &wire.TxOut{ + PkScript: sweepBtcPacket.Inputs[0].WitnessUtxo.PkScript, + Value: sweepBtcPacket.Inputs[0].WitnessUtxo.Value, + } + feeTxOut := &wire.TxOut{ + PkScript: sweepBtcPacket.Inputs[1].WitnessUtxo.PkScript, + Value: sweepBtcPacket.Inputs[1].WitnessUtxo.Value, + } + + if s.CheckCSV { + sweepBtcPacket.UnsignedTx.TxIn[0].Sequence = 1 + } + + successScript, err := s.GetSuccessScript() + if err != nil { + return nil, err + } + + signDesc := &lndclient.SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + KeyLocator: keyLocator, + }, + SignMethod: input.TaprootScriptSpendSignMethod, + WitnessScript: successScript, + Output: assetTxOut, + InputIndex: 0, + } + sig, err := signer.SignOutputRaw( + ctx, sweepBtcPacket.UnsignedTx, + []*lndclient.SignDescriptor{ + signDesc, + }, + []*wire.TxOut{ + assetTxOut, feeTxOut, + }, + ) + if err != nil { + return nil, err + } + + taprootAssetRoot, err := GenTaprootAssetRootFromProof(htlcProof) + if err != nil { + return nil, err + } + + successControlBlock, err := s.GenSuccessBtcControlBlock( + taprootAssetRoot, + ) + if err != nil { + return nil, err + } + + controlBlockBytes, err := successControlBlock.ToBytes() + if err != nil { + return nil, err + } + + return wire.TxWitness{ + preimage[:], + sig[0], + successScript, + controlBlockBytes, + }, nil +} + +// CreateTimeoutWitness creates a timeout witness for the swap. +func (s *SwapKit) CreateTimeoutWitness(ctx context.Context, + signer lndclient.SignerClient, htlcProof *proof.Proof, + sweepBtcPacket *psbt.Packet, keyLocator keychain.KeyLocator) ( + wire.TxWitness, error) { + + assetTxOut := &wire.TxOut{ + PkScript: sweepBtcPacket.Inputs[0].WitnessUtxo.PkScript, + Value: sweepBtcPacket.Inputs[0].WitnessUtxo.Value, + } + feeTxOut := &wire.TxOut{ + PkScript: sweepBtcPacket.Inputs[1].WitnessUtxo.PkScript, + Value: sweepBtcPacket.Inputs[1].WitnessUtxo.Value, + } + + sweepBtcPacket.UnsignedTx.TxIn[0].Sequence = s.CsvExpiry + + timeoutScript, err := s.GetTimeoutScript() + if err != nil { + return nil, err + } + + signDesc := &lndclient.SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + KeyLocator: keyLocator, + }, + SignMethod: input.TaprootScriptSpendSignMethod, + WitnessScript: timeoutScript, + Output: assetTxOut, + InputIndex: 0, + } + sig, err := signer.SignOutputRaw( + ctx, sweepBtcPacket.UnsignedTx, + []*lndclient.SignDescriptor{ + signDesc, + }, + []*wire.TxOut{ + assetTxOut, feeTxOut, + }, + ) + if err != nil { + return nil, err + } + + taprootAssetRoot, err := GenTaprootAssetRootFromProof(htlcProof) + if err != nil { + return nil, err + } + + timeoutControlBlock, err := s.GenTimeoutBtcControlBlock( + taprootAssetRoot, + ) + if err != nil { + return nil, err + } + + controlBlockBytes, err := timeoutControlBlock.ToBytes() + if err != nil { + return nil, err + } + + return wire.TxWitness{ + sig[0], + timeoutScript, + controlBlockBytes, + }, nil +} diff --git a/assets/tapkit.go b/assets/tapkit.go new file mode 100644 index 000000000..c5498b47f --- /dev/null +++ b/assets/tapkit.go @@ -0,0 +1,158 @@ +package assets + +import ( + "context" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/loop/assets/htlc" + "github.com/lightninglabs/taproot-assets/address" + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/commitment" + "github.com/lightninglabs/taproot-assets/proof" + "github.com/lightninglabs/taproot-assets/tappsbt" + "github.com/lightninglabs/taproot-assets/tapsend" +) + +// GenTaprootAssetRootFromProof generates the taproot asset root from the proof +// of the swap. +func GenTaprootAssetRootFromProof(proof *proof.Proof) ([]byte, error) { + assetCopy := proof.Asset.CopySpendTemplate() + + version := commitment.TapCommitmentV2 + assetCommitment, err := commitment.FromAssets(&version, assetCopy) + if err != nil { + return nil, err + } + + assetCommitment, err = commitment.TrimSplitWitnesses( + &version, assetCommitment, + ) + if err != nil { + return nil, err + } + + taprootAssetRoot := assetCommitment.TapscriptRoot(nil) + + return taprootAssetRoot[:], nil +} + +// CreateOpTrueSweepVpkt creates a VPacket that sweeps the outputs associated +// with the passed in proofs, given that their TAP script is a simple OP_TRUE. +func CreateOpTrueSweepVpkt(ctx context.Context, proofs []*proof.Proof, + addr *address.Tap, chainParams *address.ChainParams) ( + *tappsbt.VPacket, error) { + + sweepVpkt, err := tappsbt.FromProofs(proofs, chainParams, tappsbt.V1) + if err != nil { + return nil, err + } + + total := uint64(0) + for i, proof := range proofs { + inputKey := proof.InclusionProof.InternalKey + + sweepVpkt.Inputs[i].Anchor.Bip32Derivation = + []*psbt.Bip32Derivation{ + { + PubKey: inputKey.SerializeCompressed(), + }, + } + sweepVpkt.Inputs[i].Anchor.TrBip32Derivation = + []*psbt.TaprootBip32Derivation{ + { + XOnlyPubKey: schnorr.SerializePubKey( + inputKey, + ), + }, + } + + total += proof.Asset.Amount + } + + // Sanity check that the amount that we're attempting to sweep matches + // the address amount. + if total != addr.Amount { + return nil, fmt.Errorf("total amount of proofs does not " + + "match the amount of the address") + } + + /* + addressRecvVpkt, err := tappsbt.FromAddresses([]*address.Tap{addr}, 0) + if err != nil { + return nil, err + } + + sweepVpkt.Outputs = addressRecvVpkt.Outputs + */ + + // If we are sending the full value of the input asset, or sending a + // collectible, we will need to create a split with un-spendable change. + // Since we don't have any inputs selected yet, we'll use the NUMS + // script key to avoid deriving a new key for each funding attempt. If + // we need a change output, this un-spendable script key will be + // identified as such and replaced with a real one during the funding + // process. + sweepVpkt.Outputs = append(sweepVpkt.Outputs, &tappsbt.VOutput{ + Amount: 0, + Interactive: false, + Type: tappsbt.TypeSplitRoot, + AnchorOutputIndex: 0, + ScriptKey: asset.NUMSScriptKey, + // TODO(bhandras): set this to the actual internal key derived + // from the sender node, otherwise they'll lose the 1000 sats + // of the tombstone output. + AnchorOutputInternalKey: asset.NUMSPubKey, + }) + + sweepVpkt.Outputs = append(sweepVpkt.Outputs, &tappsbt.VOutput{ + AssetVersion: addr.AssetVersion, + Amount: addr.Amount, + Interactive: false, + AnchorOutputIndex: 1, + ScriptKey: asset.NewScriptKey( + &addr.ScriptKey, + ), + AnchorOutputInternalKey: &addr.InternalKey, + AnchorOutputTapscriptSibling: addr.TapscriptSibling, + ProofDeliveryAddress: &addr.ProofCourierAddr, + }) + + err = tapsend.PrepareOutputAssets(ctx, sweepVpkt) + if err != nil { + return nil, err + } + + _, _, _, controlBlock, err := htlc.CreateOpTrueLeaf() + if err != nil { + return nil, err + } + + controlBlockBytes, err := controlBlock.ToBytes() + if err != nil { + return nil, err + } + + opTrueScript, err := htlc.GetOpTrueScript() + if err != nil { + return nil, err + } + + witness := wire.TxWitness{ + opTrueScript, + controlBlockBytes, + } + + err = sweepVpkt.Outputs[0].Asset.UpdateTxWitness(0, witness) + if err != nil { + return nil, fmt.Errorf("unable to update witness: %w", err) + } + + err = sweepVpkt.Outputs[1].Asset.UpdateTxWitness(0, witness) + if err != nil { + return nil, fmt.Errorf("unable to update witness: %w", err) + } + return sweepVpkt, nil +} diff --git a/cmd/loop/asset_deposits.go b/cmd/loop/asset_deposits.go new file mode 100644 index 000000000..5538b1ab3 --- /dev/null +++ b/cmd/loop/asset_deposits.go @@ -0,0 +1,255 @@ +package main + +import ( + "context" + + "github.com/lightninglabs/loop/looprpc" + "github.com/urfave/cli" +) + +var ( + assetDepositsCommands = cli.Command{ + Name: "asset deposits", + ShortName: "ad", + Usage: "TAP asset deposit commands.", + Subcommands: []cli.Command{ + newAssetDepositCommand, + listAssetDepositsCommand, + withdrawAssetDepositCommand, + testCoSignCommand, + testKeyRevealCommand, + }, + } + + newAssetDepositCommand = cli.Command{ + Name: "new", + ShortName: "n", + Usage: "Create a new TAP asset deposit.", + Description: "Create a new TAP asset deposit.", + Action: newAssetDeposit, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "asset_id", + Usage: "The asset id of the asset to deposit.", + }, + cli.Uint64Flag{ + Name: "amt", + Usage: "the amount to deposit (in asset " + + "units).", + }, + cli.UintFlag{ + Name: "expiry", + Usage: "the deposit expiry in blocks.", + }, + }, + } + + listAssetDepositsCommand = cli.Command{ + Name: "list", + ShortName: "l", + Usage: "List TAP asset deposits.", + Description: "List TAP asset deposits.", + Flags: []cli.Flag{ + cli.UintFlag{ + Name: "min_confs", + Usage: "The minimum amount of confirmations " + + "an anchor output should have to be " + + "listed.", + }, + cli.UintFlag{ + Name: "max_confs", + Usage: "The maximum number of confirmations " + + "an anchor output could have to be " + + "listed.", + }, + }, + Action: listAssetDeposits, + } + + withdrawAssetDepositCommand = cli.Command{ + Name: "withdraw", + ShortName: "w", + Usage: "Withdraw TAP asset deposits.", + Description: "Withdraw TAP asset deposits.", + Action: withdrawAssetDeposit, + Flags: []cli.Flag{ + cli.StringSliceFlag{ + Name: "deposit_ids", + Usage: "The deposit ids of the asset " + + "deposits to withdraw.", + }, + }, + } + + testKeyRevealCommand = cli.Command{ + Name: "testkeyreveal", + ShortName: "tkr", + Usage: "Test revealing the key of a deposit to the server.", + Action: testKeyReveal, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "deposit_id", + Usage: "The deposit id of the asset deposit.", + }, + }, + } + + testCoSignCommand = cli.Command{ + Name: "testcosign", + ShortName: "tcs", + Usage: "Test co-signing a deposit to spend to an HTLC.", + Action: testCoSign, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "deposit_id", + Usage: "The deposit id of the asset deposit.", + }, + }, + } +) + +func init() { + commands = append(commands, assetDepositsCommands) +} + +func newAssetDeposit(ctx *cli.Context) error { + ctxb := context.Background() + if ctx.NArg() > 0 { + return cli.ShowCommandHelp(ctx, "newdeposit") + } + + client, cleanup, err := getAssetDepositsClient(ctx) + if err != nil { + return err + } + defer cleanup() + + assetID := ctx.String("asset_id") + amt := ctx.Uint64("amt") + expiry := int32(ctx.Uint("expiry")) + + resp, err := client.NewAssetDeposit( + ctxb, &looprpc.NewAssetDepositRequest{ + AssetId: assetID, + Amount: amt, + CsvExpiry: expiry, + }, + ) + if err != nil { + return err + } + + printJSON(resp) + + return nil +} + +func listAssetDeposits(ctx *cli.Context) error { + ctxb := context.Background() + if ctx.NArg() > 0 { + return cli.ShowCommandHelp(ctx, "list") + } + + client, cleanup, err := getAssetDepositsClient(ctx) + if err != nil { + return err + } + defer cleanup() + + resp, err := client.ListAssetDeposits( + ctxb, &looprpc.ListAssetDepositsRequest{ + MinConfs: uint32(ctx.Int("min_confs")), + MaxConfs: uint32(ctx.Int("max_confs")), + }) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} + +func withdrawAssetDeposit(ctx *cli.Context) error { + ctxb := context.Background() + if ctx.NArg() > 0 { + return cli.ShowCommandHelp(ctx, "withdraw") + } + + client, cleanup, err := getAssetDepositsClient(ctx) + if err != nil { + return err + } + defer cleanup() + + depositIDs := ctx.StringSlice("deposit_ids") + + resp, err := client.WithdrawAssetDeposits( + ctxb, &looprpc.WithdrawAssetDepositsRequest{ + DepositIds: depositIDs, + }, + ) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} + +func testKeyReveal(ctx *cli.Context) error { + ctxb := context.Background() + if ctx.NArg() > 0 { + return cli.ShowCommandHelp(ctx, "testkeyreveal") + } + + client, cleanup, err := getAssetDepositsClient(ctx) + if err != nil { + return err + } + defer cleanup() + + depositID := ctx.String("deposit_id") + + resp, err := client.RevealAssetDepositKey( + ctxb, &looprpc.RevealAssetDepositKeyRequest{ + DepositId: depositID, + }, + ) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} + +func testCoSign(ctx *cli.Context) error { + ctxb := context.Background() + if ctx.NArg() > 0 { + return cli.ShowCommandHelp(ctx, "testcosign") + } + + client, cleanup, err := getAssetDepositsClient(ctx) + if err != nil { + return err + } + defer cleanup() + + depositID := ctx.String("deposit_id") + + resp, err := client.TestCoSignAssetDepositHTLC( + ctxb, &looprpc.TestCoSignAssetDepositHTLCRequest{ + DepositId: depositID, + }, + ) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} diff --git a/cmd/loop/main.go b/cmd/loop/main.go index 4544dfac4..f068959bb 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -185,22 +185,44 @@ func main() { } } -func getClient(ctx *cli.Context) (looprpc.SwapClientClient, func(), error) { +func getConn(ctx *cli.Context) (*grpc.ClientConn, func(), error) { rpcServer := ctx.GlobalString("rpcserver") tlsCertPath, macaroonPath, err := extractPathArgs(ctx) if err != nil { return nil, nil, err } + conn, err := getClientConn(rpcServer, tlsCertPath, macaroonPath) if err != nil { return nil, nil, err } cleanup := func() { conn.Close() } + return conn, cleanup, nil +} + +func getClient(ctx *cli.Context) (looprpc.SwapClientClient, func(), error) { + conn, cleanup, err := getConn(ctx) + if err != nil { + return nil, nil, err + } + loopClient := looprpc.NewSwapClientClient(conn) return loopClient, cleanup, nil } +func getAssetDepositsClient(ctx *cli.Context) ( + looprpc.AssetDepositClientClient, func(), error) { + + conn, cleanup, err := getConn(ctx) + if err != nil { + return nil, nil, err + } + + assetDepositsClient := looprpc.NewAssetDepositClientClient(conn) + return assetDepositsClient, cleanup, nil +} + func getMaxRoutingFee(amt btcutil.Amount) btcutil.Amount { return swap.CalcFee(amt, maxRoutingFeeBase, maxRoutingFeeRate) } diff --git a/go.mod b/go.mod index 97a54488c..754d100d6 100644 --- a/go.mod +++ b/go.mod @@ -29,13 +29,13 @@ require ( github.com/lightningnetwork/lnd/clock v1.1.1 github.com/lightningnetwork/lnd/queue v1.1.1 github.com/lightningnetwork/lnd/ticker v1.1.1 - github.com/lightningnetwork/lnd/tlv v1.3.1 + github.com/lightningnetwork/lnd/tlv v1.3.2 github.com/lightningnetwork/lnd/tor v1.1.6 github.com/ory/dockertest/v3 v3.10.0 github.com/stretchr/testify v1.10.0 github.com/urfave/cli v1.22.14 go.etcd.io/bbolt v1.3.11 - golang.org/x/sync v0.12.0 + golang.org/x/sync v0.13.0 google.golang.org/grpc v1.64.1 google.golang.org/protobuf v1.34.2 gopkg.in/macaroon-bakery.v2 v2.3.0 @@ -72,8 +72,8 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect github.com/decred/dcrd/lru v1.1.2 // indirect - github.com/docker/cli v28.0.1+incompatible // indirect - github.com/docker/docker v28.0.1+incompatible // indirect + github.com/docker/cli v28.1.1+incompatible // indirect + github.com/docker/docker v28.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect @@ -101,12 +101,12 @@ require ( github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgproto3/v2 v2.3.3 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgtype v1.14.0 // indirect - github.com/jackc/pgx/v4 v4.18.2 // indirect - github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgtype v1.14.4 // indirect + github.com/jackc/pgx/v4 v4.18.3 // indirect + github.com/jackc/pgx/v5 v5.7.4 // indirect github.com/jackc/puddle v1.3.0 // indirect - github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jackpal/gateway v1.0.5 // indirect github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad // indirect github.com/jonboulle/clockwork v0.2.2 // indirect @@ -120,11 +120,11 @@ require ( github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.3 // indirect github.com/lightninglabs/neutrino v0.16.1 // indirect github.com/lightninglabs/neutrino/cache v1.1.2 // indirect - github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb // indirect + github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9 // indirect github.com/lightningnetwork/lnd/fn/v2 v2.0.8 // indirect github.com/lightningnetwork/lnd/healthcheck v1.2.6 // indirect github.com/lightningnetwork/lnd/kvdb v1.4.16 // indirect - github.com/lightningnetwork/lnd/sqldb v1.0.9 // indirect + github.com/lightningnetwork/lnd/sqldb v1.0.10 // indirect github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect @@ -180,13 +180,13 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.23.0 // indirect - golang.org/x/crypto v0.36.0 // indirect + golang.org/x/crypto v0.37.0 // indirect golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/net v0.39.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/term v0.31.0 // indirect + golang.org/x/text v0.24.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.24.0 // indirect google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect @@ -215,4 +215,27 @@ replace github.com/lightninglabs/loop/swapserverrpc => ./swapserverrpc replace github.com/lightninglabs/loop/looprpc => ./looprpc -go 1.23.9 +// Temporary replace to add SubmitPackage support (https://github.com/btcsuite/btcwallet/pull/1009). +replace github.com/btcsuite/btcwallet => github.com/bhandras/btcwallet v0.11.1-0.20250507171803-0de1c46b1cfc + +// Temporary replace to add SubmitPackage support to btcd (https://github.com/btcsuite/btcd/pull/2366). +replace github.com/btcsuite/btcd => github.com/bhandras/btcd v0.22.0-beta.0.20250507171227-f18160c86e92 + +// Temporary replace to add SubmitPackage support to lnd (https://github.com/lightningnetwork/lnd/pull/9784). +replace github.com/lightningnetwork/lnd => github.com/bhandras/lnd v0.8.0-beta-rc3.0.20250717123715-6cda96a60994 + +// Temporary replace to make lnd compile with the SubmitPackage changes. +replace github.com/lightningnetwork/lnd/sqldb => github.com/bhandras/lnd/sqldb v0.0.0-20250716041958-643fbb8af65b + +// Temporary replace to include client API for SubmitPackage in lndclient (https://github.com/lightninglabs/lndclient/pull/223) +replace github.com/lightninglabs/lndclient => github.com/lightninglabs/lndclient v1.0.1-0.20250717123354-cf534c9968b9 + +// Temporary replace to experimentally change all bitcoin transactions to use v3 (https://github.com/bhandras/taproot-assets/tree/v3-experimental) +replace github.com/lightninglabs/taproot-assets => github.com/bhandras/taproot-assets v0.0.0-20250717122952-ca46f25f6c3b + +// Temporary replace to make taproot-assets compile with the v3 changes. +replace github.com/lightninglabs/taproot-assets/taprpc => github.com/bhandras/taproot-assets/taprpc v0.0.0-20250605133854-360a25355248 + +go 1.23.10 + +toolchain go1.24.5 diff --git a/go.sum b/go.sum index 97271d03d..baec250bf 100644 --- a/go.sum +++ b/go.sum @@ -642,19 +642,23 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bhandras/btcd v0.22.0-beta.0.20250507171227-f18160c86e92 h1:/H5Dv5VoKqsXkI3iZc6D3xonibJS6YdPHgAa2XkFHi0= +github.com/bhandras/btcd v0.22.0-beta.0.20250507171227-f18160c86e92/go.mod h1:OmM4kFtB0klaG/ZqT86rQiyw/1iyXlJgc3UHClPhhbs= +github.com/bhandras/btcwallet v0.11.1-0.20250507171803-0de1c46b1cfc h1:RvT6udxYM857Kvj5fEkWhTo0wAT0t7R7oOgYSoLJOLY= +github.com/bhandras/btcwallet v0.11.1-0.20250507171803-0de1c46b1cfc/go.mod h1:PZ4WgE93vP5TBchtfrlvf5GT6P9ul0tM8rTH1BSYloo= +github.com/bhandras/lnd v0.8.0-beta-rc3.0.20250717123715-6cda96a60994 h1:61nMss7Syk1g1oO6c8vMP/YSthfi0HUfoaGyt+0JR5Y= +github.com/bhandras/lnd v0.8.0-beta-rc3.0.20250717123715-6cda96a60994/go.mod h1:uq19F2JuEISti9T23gfPWkeNFL9O1lre5g/OBTc9mRo= +github.com/bhandras/lnd/sqldb v0.0.0-20250716041958-643fbb8af65b h1:Eb1tarG9pgXfyYVWo2HzZU30W3SMd7L/6T7dFdviGIU= +github.com/bhandras/lnd/sqldb v0.0.0-20250716041958-643fbb8af65b/go.mod h1:JrbvoQOUPXN1Mazh/KRi1LZf0kfGZsH8OY3mF3niqS8= +github.com/bhandras/taproot-assets v0.0.0-20250717122952-ca46f25f6c3b h1:240ExplnjMADe53beoi9MKmj+8O85GuKzwuuHso3C0Y= +github.com/bhandras/taproot-assets v0.0.0-20250717122952-ca46f25f6c3b/go.mod h1:rF+GwuUVuDVUejAHsUCml4Nru9xnl7A4YZQfR4qLMzY= +github.com/bhandras/taproot-assets/taprpc v0.0.0-20250605133854-360a25355248 h1:6YNvikjQFxksYtq/yutSrX6sRhsEa+jeAR+fGuyCtqQ= +github.com/bhandras/taproot-assets/taprpc v0.0.0-20250605133854-360a25355248/go.mod h1:vOM2Ap2wYhEZjiJU7bNNg+e5tDxkvRAuyXwf/KQ4tgo= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= -github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= -github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6 h1:8n9k3I7e8DkpdQ5YAP4j8ly/LSsbe6qX9vmVbrUGvVw= -github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6/go.mod h1:OmM4kFtB0klaG/ZqT86rQiyw/1iyXlJgc3UHClPhhbs= -github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= -github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= -github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= github.com/btcsuite/btcd/btcutil/psbt v1.1.10 h1:TC1zhxhFfhnGqoPjsrlEpoqzh+9TPOHrCgnPR47Mj9I= @@ -668,9 +672,6 @@ github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c h1:4HxD1lBUGUddhzg github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ= github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084 h1:y3bvkt8ki0KX35eUEU8XShRHusz1S+55QwXUTmxn888= github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE= -github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcwallet v0.16.14 h1:CofysgmI1ednkLsXontAdBoXJkbiim7unXnFKhLLjnE= -github.com/btcsuite/btcwallet v0.16.14/go.mod h1:H6dfoZcWPonM2wbVsR2ZBY0PKNZKdQyLAmnX8vL9JFA= github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 h1:Rr0njWI3r341nhSPesKQ2JF+ugDSzdPoeckS75SeDZk= github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5/go.mod h1:+tXJ3Ym0nlQc/iHSwW1qzjmPs3ev+UVWMbGgfV1OZqU= github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 h1:YEO+Lx1ZJJAtdRrjuhXjWrYsmAk26wLTlNzxt2q0lhk= @@ -685,9 +686,7 @@ github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JG github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= -github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk= @@ -758,10 +757,10 @@ github.com/dhui/dktest v0.4.5 h1:uUfYBIVREmj/Rw6MvgmqNAYzTiKOHJak+enB5Di73MM= github.com/dhui/dktest v0.4.5/go.mod h1:tmcyeHDKagvlDrz7gDKq4UAJOLIfVZYkfD5OnHDwcCo= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v28.0.1+incompatible h1:g0h5NQNda3/CxIsaZfH4Tyf6vpxFth7PYl3hgCPOKzs= -github.com/docker/cli v28.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0= -github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/cli v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5MdSpiItp8k= +github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I= +github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -1011,29 +1010,32 @@ github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwX github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.14.4 h1:fKuNiCumbKTAIxQwXfB/nsrnkEI6bPJrrSiMKgbJ2j8= +github.com/jackc/pgtype v1.14.4/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.18.2 h1:xVpYkNR5pk5bMCZGfClbO962UIqVABcAGt7ha1s/FeU= github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= -github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= -github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg= +github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= -github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad h1:heFfj7z0pGsNCekUlsFhO2jstxO4b5iQ665LjwM5mDc= @@ -1106,8 +1108,8 @@ github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.3 h1:NuDp6Z+QNMSzZ/+RzWsjgAgQSr/REDxTiHmTczZxlXA= github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.3/go.mod h1:bDnEKRN1u13NFBuy/C+bFLhxA5bfd3clT25y76QY0AM= -github.com/lightninglabs/lndclient v0.19.0-12 h1:aSIKfnvnHKiyFWppUGHJG5fn8VoF5WG5Lx958ksLmqs= -github.com/lightninglabs/lndclient v0.19.0-12/go.mod h1:cicoJY1AwZuRVXGD8Knp50TRT7TGBmw1k37uPQsGQiw= +github.com/lightninglabs/lndclient v1.0.1-0.20250717123354-cf534c9968b9 h1:W0fnqItIl0OeyZYfIphj13Sfer5Q3pTE0PJjR/MUEfg= +github.com/lightninglabs/lndclient v1.0.1-0.20250717123354-cf534c9968b9/go.mod h1:aven9VZtddSIXC1IH60wo+vG0EEfnZsmOgDV+yQvIKo= github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2 h1:eFjp1dIB2BhhQp/THKrjLdlYuPugO9UU4kDqu91OX/Q= github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY= github.com/lightninglabs/neutrino v0.16.1 h1:5Kz4ToxncEVkpKC6fwUjXKtFKJhuxlG3sBB3MdJTJjs= @@ -1116,14 +1118,8 @@ github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3 github.com/lightninglabs/neutrino/cache v1.1.2/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo= github.com/lightninglabs/protobuf-go-hex-display v1.34.2-hex-display h1:w7FM5LH9Z6CpKxl13mS48idsu6F+cEZf0lkyiV+Dq9g= github.com/lightninglabs/protobuf-go-hex-display v1.34.2-hex-display/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -github.com/lightninglabs/taproot-assets v0.6.1 h1:98XCk7nvAridyE67uct0NDVpyY1evpIdvPQpeNElskM= -github.com/lightninglabs/taproot-assets v0.6.1/go.mod h1:rF+GwuUVuDVUejAHsUCml4Nru9xnl7A4YZQfR4qLMzY= -github.com/lightninglabs/taproot-assets/taprpc v1.0.8-0.20250617163017-cf2a5e5bb47c h1:Fzob+kYq68uPuaEd78rVa/Jvjn/Rp4rLcG7xRTOxK7Y= -github.com/lightninglabs/taproot-assets/taprpc v1.0.8-0.20250617163017-cf2a5e5bb47c/go.mod h1:vOM2Ap2wYhEZjiJU7bNNg+e5tDxkvRAuyXwf/KQ4tgo= -github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb h1:yfM05S8DXKhuCBp5qSMZdtSwvJ+GFzl94KbXMNB1JDY= -github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI= -github.com/lightningnetwork/lnd v0.19.2-beta h1:3SKVrKYFY4IJLlrMf7cDzZcBeT+MxjI9Xy6YpY+EEX4= -github.com/lightningnetwork/lnd v0.19.2-beta/go.mod h1:+yKUfIGKKYRHGewgzQ6xi0S26DIfBiMv1zCdB3m6YxA= +github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9 h1:6D3LrdagJweLLdFm1JNodZsBk6iU4TTsBBFLQ4yiXfI= +github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9/go.mod h1:EDqJ3MuZIbMq0QI1czTIKDJ/GS8S14RXPwapHw8cw6w= github.com/lightningnetwork/lnd/cert v1.2.2 h1:71YK6hogeJtxSxw2teq3eGeuy4rHGKcFf0d0Uy4qBjI= github.com/lightningnetwork/lnd/cert v1.2.2/go.mod h1:jQmFn/Ez4zhDgq2hnYSw8r35bqGVxViXhX6Cd7HXM6U= github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0= @@ -1136,12 +1132,10 @@ github.com/lightningnetwork/lnd/kvdb v1.4.16 h1:9BZgWdDfjmHRHLS97cz39bVuBAqMc4/p github.com/lightningnetwork/lnd/kvdb v1.4.16/go.mod h1:HW+bvwkxNaopkz3oIgBV6NEnV4jCEZCACFUcNg4xSjM= github.com/lightningnetwork/lnd/queue v1.1.1 h1:99ovBlpM9B0FRCGYJo6RSFDlt8/vOkQQZznVb18iNMI= github.com/lightningnetwork/lnd/queue v1.1.1/go.mod h1:7A6nC1Qrm32FHuhx/mi1cieAiBZo5O6l8IBIoQxvkz4= -github.com/lightningnetwork/lnd/sqldb v1.0.9 h1:7OHi+Hui823mB/U9NzCdlZTAGSVdDCbjp33+6d/Q+G0= -github.com/lightningnetwork/lnd/sqldb v1.0.9/go.mod h1:OG09zL/PHPaBJefp4HsPz2YLUJ+zIQHbpgCtLnOx8I4= github.com/lightningnetwork/lnd/ticker v1.1.1 h1:J/b6N2hibFtC7JLV77ULQp++QLtCwT6ijJlbdiZFbSM= github.com/lightningnetwork/lnd/ticker v1.1.1/go.mod h1:waPTRAAcwtu7Ji3+3k+u/xH5GHovTsCoSVpho0KDvdA= -github.com/lightningnetwork/lnd/tlv v1.3.1 h1:o7CZg06y+rJZfUMAo0WzBLr0pgBWCzrt0f9gpujYUzk= -github.com/lightningnetwork/lnd/tlv v1.3.1/go.mod h1:pJuiBj1ecr1WWLOtcZ+2+hu9Ey25aJWFIsjmAoPPnmc= +github.com/lightningnetwork/lnd/tlv v1.3.2 h1:MO4FCk7F4k5xPMqVZF6Nb/kOpxlwPrUQpYjmyKny5s0= +github.com/lightningnetwork/lnd/tlv v1.3.2/go.mod h1:pJuiBj1ecr1WWLOtcZ+2+hu9Ey25aJWFIsjmAoPPnmc= github.com/lightningnetwork/lnd/tor v1.1.6 h1:WHUumk7WgU6BUFsqHuqszI9P6nfhMeIG+rjJBlVE6OE= github.com/lightningnetwork/lnd/tor v1.1.6/go.mod h1:qSRB8llhAK+a6kaTPWOLLXSZc6Hg8ZC0mq1sUQ/8JfI= github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw= @@ -1191,13 +1185,10 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= @@ -1425,8 +1416,11 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1489,7 +1483,6 @@ golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20150829230318-ea47fc708ee3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1558,8 +1551,11 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1606,8 +1602,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1707,8 +1703,11 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1718,8 +1717,11 @@ golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1736,8 +1738,9 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/loopd/daemon.go b/loopd/daemon.go index 7212933ad..13cd8b1a6 100644 --- a/loopd/daemon.go +++ b/loopd/daemon.go @@ -7,6 +7,7 @@ import ( "fmt" "net" "net/http" + _ "net/http/pprof" "strings" "sync" "sync/atomic" @@ -16,6 +17,7 @@ import ( "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop" "github.com/lightninglabs/loop/assets" + asset_deposit "github.com/lightninglabs/loop/assets/deposit" "github.com/lightninglabs/loop/instantout" "github.com/lightninglabs/loop/instantout/reservation" "github.com/lightninglabs/loop/loopdb" @@ -132,6 +134,16 @@ func (d *Daemon) Start() error { return errOnlyStartOnce } + go func() { + http.Handle("/", http.RedirectHandler( + "/debug/pprof", http.StatusSeeOther, + )) + + listenAddr := fmt.Sprintf(":%d", 4321) + infof("Starting profile server at %s", listenAddr) + fmt.Println(http.ListenAndServe(listenAddr, nil)) // nolint: gosec + }() + network := lndclient.Network(d.cfg.Network) var err error @@ -248,6 +260,11 @@ func (d *Daemon) startWebServers() error { ) loop_looprpc.RegisterSwapClientServer(d.grpcServer, d) + // Register the asset deposit sub-server within the grpc server. + loop_looprpc.RegisterAssetDepositClientServer( + d.grpcServer, d.swapClientServer.assetDepositServer, + ) + // Register our debug server if it is compiled in. d.registerDebugServer() @@ -417,6 +434,12 @@ func (d *Daemon) initialize(withMacaroonService bool) error { infof("Successfully migrated boltdb") } + // Lnd's GetInfo call supplies us with the current block height. + info, err := d.lnd.Client.GetInfo(d.mainCtx) + if err != nil { + return err + } + // Now that we know where the database will live, we'll go ahead and // open up the default implementation of it. chainParams, err := lndclient.Network(d.cfg.Network).ChainParams() @@ -494,6 +517,10 @@ func (d *Daemon) initialize(withMacaroonService bool) error { swapClient.Conn, ) + assetDepositClient := loop_swaprpc.NewAssetDepositServiceClient( + swapClient.Conn, + ) + // Both the client RPC server and the swap server client should stop // on main context cancel. So we create it early and pass it down. d.mainCtx, d.mainCtxCancel = context.WithCancel(context.Background()) @@ -575,6 +602,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error { depositManager *deposit.Manager withdrawalManager *withdraw.Manager staticLoopInManager *loopin.Manager + assetDepositManager *asset_deposit.Manager ) // Static address manager setup. @@ -688,8 +716,26 @@ func (d *Daemon) initialize(withMacaroonService bool) error { instantOutManager = instantout.NewInstantOutManager( instantOutConfig, int32(blockHeight), ) + + if d.assetClient != nil { + depositStore := asset_deposit.NewSQLStore( + loopdb.NewTypedStore[asset_deposit.Querier]( + baseDb, + ), clock.NewDefaultClock(), d.lnd.ChainParams, + ) + assetDepositManager = asset_deposit.NewManager( + assetDepositClient, d.lnd.WalletKit, + d.lnd.Signer, d.lnd.ChainNotifier, + d.assetClient, depositStore, d.lnd.ChainParams, + ) + } + } + // If the deposit manager is nil, the server will reutrn Unimplemented + // error for all RPCs. + assetDepositServer := asset_deposit.NewServer(assetDepositManager) + // Now finally fully initialize the swap client RPC server instance. d.swapClientServer = swapClientServer{ config: d.cfg, @@ -708,6 +754,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error { withdrawalManager: withdrawalManager, staticLoopInManager: staticLoopInManager, assetClient: d.assetClient, + assetDepositServer: assetDepositServer, } // Retrieve all currently existing swaps from the database. @@ -974,6 +1021,21 @@ func (d *Daemon) initialize(withMacaroonService bool) error { } } + if assetDepositManager != nil { + d.wg.Add(1) + + go func() { + defer d.wg.Done() + + err = assetDepositManager.Run( + d.mainCtx, info.BlockHeight, + ) + if err != nil && !errors.Is(context.Canceled, err) { + d.internalErrChan <- err + } + }() + } + // Last, start our internal error handler. This will return exactly one // error or nil on the main error channel to inform the caller that // something went wrong or that shutdown is complete. We don't add to diff --git a/loopd/log.go b/loopd/log.go index 0b29d4b32..830861db3 100644 --- a/loopd/log.go +++ b/loopd/log.go @@ -7,6 +7,7 @@ import ( "github.com/lightninglabs/aperture/l402" "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop" + "github.com/lightninglabs/loop/assets/deposit" "github.com/lightninglabs/loop/fsm" "github.com/lightninglabs/loop/instantout" "github.com/lightninglabs/loop/instantout/reservation" @@ -92,6 +93,9 @@ func SetupLoggers(root *build.SubLoggerManager, intercept signal.Interceptor) { lnd.AddSubLogger( root, sweep.Subsystem, intercept, sweep.UseLogger, ) + lnd.AddSubLogger( + root, deposit.Subsystem, intercept, deposit.UseLogger, + ) } // genSubLogger creates a logger for a subsystem. We provide an instance of diff --git a/loopd/swapclient_server.go b/loopd/swapclient_server.go index 313db6fc3..61bfdf348 100644 --- a/loopd/swapclient_server.go +++ b/loopd/swapclient_server.go @@ -22,6 +22,7 @@ import ( "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop" "github.com/lightninglabs/loop/assets" + asset_deposit "github.com/lightninglabs/loop/assets/deposit" "github.com/lightninglabs/loop/fsm" "github.com/lightninglabs/loop/instantout" "github.com/lightninglabs/loop/instantout/reservation" @@ -98,6 +99,7 @@ type swapClientServer struct { withdrawalManager *withdraw.Manager staticLoopInManager *loopin.Manager assetClient *assets.TapdClient + assetDepositServer *asset_deposit.Server swaps map[lntypes.Hash]loop.SwapInfo subscribers map[int]chan<- interface{} statusChan chan loop.SwapInfo diff --git a/loopdb/sqlc/asset_deposits.sql.go b/loopdb/sqlc/asset_deposits.sql.go new file mode 100644 index 000000000..8bba9c751 --- /dev/null +++ b/loopdb/sqlc/asset_deposits.sql.go @@ -0,0 +1,318 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.25.0 +// source: asset_deposits.sql + +package sqlc + +import ( + "context" + "database/sql" + "time" +) + +const addAssetDeposit = `-- name: AddAssetDeposit :exec +INSERT INTO asset_deposits ( + deposit_id, + protocol_version, + created_at, + asset_id, + amount, + client_script_pubkey, + server_script_pubkey, + client_internal_pubkey, + server_internal_pubkey, + server_internal_key, + client_key_family, + client_key_index, + expiry, + addr +) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) +` + +type AddAssetDepositParams struct { + DepositID string + ProtocolVersion int32 + CreatedAt time.Time + AssetID []byte + Amount int64 + ClientScriptPubkey []byte + ServerScriptPubkey []byte + ClientInternalPubkey []byte + ServerInternalPubkey []byte + ServerInternalKey []byte + ClientKeyFamily int32 + ClientKeyIndex int32 + Expiry int32 + Addr string +} + +func (q *Queries) AddAssetDeposit(ctx context.Context, arg AddAssetDepositParams) error { + _, err := q.db.ExecContext(ctx, addAssetDeposit, + arg.DepositID, + arg.ProtocolVersion, + arg.CreatedAt, + arg.AssetID, + arg.Amount, + arg.ClientScriptPubkey, + arg.ServerScriptPubkey, + arg.ClientInternalPubkey, + arg.ServerInternalPubkey, + arg.ServerInternalKey, + arg.ClientKeyFamily, + arg.ClientKeyIndex, + arg.Expiry, + arg.Addr, + ) + return err +} + +const getActiveAssetDeposits = `-- name: GetActiveAssetDeposits :many +SELECT d.deposit_id, d.protocol_version, d.created_at, d.asset_id, d.amount, d.client_script_pubkey, d.server_script_pubkey, d.client_internal_pubkey, d.server_internal_pubkey, d.server_internal_key, d.expiry, d.client_key_family, d.client_key_index, d.addr, d.confirmation_height, d.outpoint, d.pk_script, d.sweep_addr, u.update_state, u.update_timestamp +FROM asset_deposits d +JOIN asset_deposit_updates u + ON u.deposit_id = d.deposit_id +WHERE u.id = ( + SELECT id + FROM asset_deposit_updates + WHERE deposit_id = d.deposit_id + ORDER BY update_timestamp DESC + LIMIT 1 +) +AND u.update_state IN (0, 1, 2, 3, 4, 5, 6) +` + +type GetActiveAssetDepositsRow struct { + DepositID string + ProtocolVersion int32 + CreatedAt time.Time + AssetID []byte + Amount int64 + ClientScriptPubkey []byte + ServerScriptPubkey []byte + ClientInternalPubkey []byte + ServerInternalPubkey []byte + ServerInternalKey []byte + Expiry int32 + ClientKeyFamily int32 + ClientKeyIndex int32 + Addr string + ConfirmationHeight sql.NullInt32 + Outpoint sql.NullString + PkScript []byte + SweepAddr sql.NullString + UpdateState int32 + UpdateTimestamp time.Time +} + +func (q *Queries) GetActiveAssetDeposits(ctx context.Context) ([]GetActiveAssetDepositsRow, error) { + rows, err := q.db.QueryContext(ctx, getActiveAssetDeposits) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetActiveAssetDepositsRow + for rows.Next() { + var i GetActiveAssetDepositsRow + if err := rows.Scan( + &i.DepositID, + &i.ProtocolVersion, + &i.CreatedAt, + &i.AssetID, + &i.Amount, + &i.ClientScriptPubkey, + &i.ServerScriptPubkey, + &i.ClientInternalPubkey, + &i.ServerInternalPubkey, + &i.ServerInternalKey, + &i.Expiry, + &i.ClientKeyFamily, + &i.ClientKeyIndex, + &i.Addr, + &i.ConfirmationHeight, + &i.Outpoint, + &i.PkScript, + &i.SweepAddr, + &i.UpdateState, + &i.UpdateTimestamp, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getAssetDepositServerInternalKey = `-- name: GetAssetDepositServerInternalKey :one +SELECT server_internal_key +FROM asset_deposits +WHERE deposit_id = $1 +` + +func (q *Queries) GetAssetDepositServerInternalKey(ctx context.Context, depositID string) ([]byte, error) { + row := q.db.QueryRowContext(ctx, getAssetDepositServerInternalKey, depositID) + var server_internal_key []byte + err := row.Scan(&server_internal_key) + return server_internal_key, err +} + +const getAssetDeposits = `-- name: GetAssetDeposits :many +SELECT d.deposit_id, d.protocol_version, d.created_at, d.asset_id, d.amount, d.client_script_pubkey, d.server_script_pubkey, d.client_internal_pubkey, d.server_internal_pubkey, d.server_internal_key, d.expiry, d.client_key_family, d.client_key_index, d.addr, d.confirmation_height, d.outpoint, d.pk_script, d.sweep_addr, u.update_state, u.update_timestamp +FROM asset_deposits d +JOIN asset_deposit_updates u ON u.id = ( + SELECT id + FROM asset_deposit_updates + WHERE deposit_id = d.deposit_id + ORDER BY update_timestamp DESC + LIMIT 1 +) +ORDER BY d.created_at ASC +` + +type GetAssetDepositsRow struct { + DepositID string + ProtocolVersion int32 + CreatedAt time.Time + AssetID []byte + Amount int64 + ClientScriptPubkey []byte + ServerScriptPubkey []byte + ClientInternalPubkey []byte + ServerInternalPubkey []byte + ServerInternalKey []byte + Expiry int32 + ClientKeyFamily int32 + ClientKeyIndex int32 + Addr string + ConfirmationHeight sql.NullInt32 + Outpoint sql.NullString + PkScript []byte + SweepAddr sql.NullString + UpdateState int32 + UpdateTimestamp time.Time +} + +func (q *Queries) GetAssetDeposits(ctx context.Context) ([]GetAssetDepositsRow, error) { + rows, err := q.db.QueryContext(ctx, getAssetDeposits) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetAssetDepositsRow + for rows.Next() { + var i GetAssetDepositsRow + if err := rows.Scan( + &i.DepositID, + &i.ProtocolVersion, + &i.CreatedAt, + &i.AssetID, + &i.Amount, + &i.ClientScriptPubkey, + &i.ServerScriptPubkey, + &i.ClientInternalPubkey, + &i.ServerInternalPubkey, + &i.ServerInternalKey, + &i.Expiry, + &i.ClientKeyFamily, + &i.ClientKeyIndex, + &i.Addr, + &i.ConfirmationHeight, + &i.Outpoint, + &i.PkScript, + &i.SweepAddr, + &i.UpdateState, + &i.UpdateTimestamp, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const markDepositConfirmed = `-- name: MarkDepositConfirmed :exec +UPDATE asset_deposits +SET confirmation_height = $2, outpoint = $3, pk_script = $4 +WHERE deposit_id = $1 +` + +type MarkDepositConfirmedParams struct { + DepositID string + ConfirmationHeight sql.NullInt32 + Outpoint sql.NullString + PkScript []byte +} + +func (q *Queries) MarkDepositConfirmed(ctx context.Context, arg MarkDepositConfirmedParams) error { + _, err := q.db.ExecContext(ctx, markDepositConfirmed, + arg.DepositID, + arg.ConfirmationHeight, + arg.Outpoint, + arg.PkScript, + ) + return err +} + +const setAssetDepositServerInternalKey = `-- name: SetAssetDepositServerInternalKey :exec +UPDATE asset_deposits +SET server_internal_key = $2 +WHERE deposit_id = $1 +AND server_internal_key IS NULL +` + +type SetAssetDepositServerInternalKeyParams struct { + DepositID string + ServerInternalKey []byte +} + +func (q *Queries) SetAssetDepositServerInternalKey(ctx context.Context, arg SetAssetDepositServerInternalKeyParams) error { + _, err := q.db.ExecContext(ctx, setAssetDepositServerInternalKey, arg.DepositID, arg.ServerInternalKey) + return err +} + +const setAssetDepositSweepAddr = `-- name: SetAssetDepositSweepAddr :exec +UPDATE asset_deposits +SET sweep_addr = $2 +WHERE deposit_id = $1 +` + +type SetAssetDepositSweepAddrParams struct { + DepositID string + SweepAddr sql.NullString +} + +func (q *Queries) SetAssetDepositSweepAddr(ctx context.Context, arg SetAssetDepositSweepAddrParams) error { + _, err := q.db.ExecContext(ctx, setAssetDepositSweepAddr, arg.DepositID, arg.SweepAddr) + return err +} + +const updateDepositState = `-- name: UpdateDepositState :exec +INSERT INTO asset_deposit_updates ( + deposit_id, + update_state, + update_timestamp +) VALUES ($1, $2, $3) +` + +type UpdateDepositStateParams struct { + DepositID string + UpdateState int32 + UpdateTimestamp time.Time +} + +func (q *Queries) UpdateDepositState(ctx context.Context, arg UpdateDepositStateParams) error { + _, err := q.db.ExecContext(ctx, updateDepositState, arg.DepositID, arg.UpdateState, arg.UpdateTimestamp) + return err +} diff --git a/loopdb/sqlc/migrations/000017_asset_deposits.down.sql b/loopdb/sqlc/migrations/000017_asset_deposits.down.sql new file mode 100644 index 000000000..3ac57554b --- /dev/null +++ b/loopdb/sqlc/migrations/000017_asset_deposits.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS asset_deposits; diff --git a/loopdb/sqlc/migrations/000017_asset_deposits.up.sql b/loopdb/sqlc/migrations/000017_asset_deposits.up.sql new file mode 100644 index 000000000..5603f6b79 --- /dev/null +++ b/loopdb/sqlc/migrations/000017_asset_deposits.up.sql @@ -0,0 +1,97 @@ +CREATE TABLE IF NOT EXISTS asset_deposits ( + deposit_id TEXT PRIMARY KEY, + + -- protocol_version is the protocol version that the deposit was + -- created with. + protocol_version INTEGER NOT NULL, + + -- created_at is the time at which the deposit was created. + created_at TIMESTAMP NOT NULL, + + -- asset_id is the asset that is being deposited. + asset_id BLOB NOT NULL, + + -- amount is the amount of the deposit in asset units. + amount BIGINT NOT NULL, + + -- client_script_pubkey is the key used for the deposit script path as well + -- as the ephemeral key used for deriving the client's internal key. + client_script_pubkey BLOB NOT NULL, + + -- server_script_pubkey is the server's key that is used to construct the + -- deposit spending HTLC. + server_script_pubkey BLOB NOT NULL, + + -- client_internal_pubkey is the key derived from the shared secret + -- which is derived from the client's script key. + client_internal_pubkey BLOB NOT NULL, + + -- server_internal_pubkey is the server side public key that is used to + -- construct the 2-of-2 MuSig2 anchor output that holds the deposited + -- funds. + server_internal_pubkey BLOB NOT NULL, + + -- server_internal_key is the revealed private key corresponding to the + -- server's internal public key. It is only revealed when the deposit is + -- cooperatively withdrawn and therefore may be NULL. Note that the value + -- may be encrypted. + server_internal_key BYTEA, + + -- expiry denotes the CSV delay at which funds at a specific static address + -- can be swept back to the client. + expiry INT NOT NULL, + + -- client_key_family is the key family of the client's script public key + -- from the client's lnd wallet. + client_key_family INT NOT NULL, + + -- client_key_index is the key index of the client's script public key from + -- the client's lnd wallet. + client_key_index INT NOT NULL, + + -- addr is the TAP deposit address that the client should send the funds to. + addr TEXT NOT NULL UNIQUE, + + -- confirmation_height is the block height at which the deposit was + -- confirmed on-chain. + confirmation_height INT, + + -- outpoint is the outpoint of the confirmed deposit. + outpoint TEXT, + + -- pk_script is the pkscript of the deposit anchor output. + pk_script BLOB, + + -- sweep_addr is the address we'll use to sweep back the deposit to if it + -- has timed out or withdrawn cooperatively. + sweep_addr TEXT +); + +-- asset_deposit_updates contains all the updates to an asset deposit. +CREATE TABLE IF NOT EXISTS asset_deposit_updates ( + -- id is the auto incrementing primary key. + id INTEGER PRIMARY KEY, + + -- deposit_id is the unique identifier for the deposit. + deposit_id TEXT NOT NULL REFERENCES asset_deposits(deposit_id), + + -- update_state is the state of the deposit at the time of the update. + update_state INT NOT NULL, + + -- update_timestamp is the timestamp of the update. + update_timestamp TIMESTAMP NOT NULL +); + +-- asset_deposit_leased_utxos contains all the UTXOs that were leased to a +-- particular deposit. These leased UTXOs are used to fund the deposit timeout +-- sweep transaction. +CREATE TABLE IF NOT EXISTS asset_deposit_leased_utxos ( + -- id is the auto incrementing primary key. + id INTEGER PRIMARY KEY, + + -- deposit_id is the unique identifier for the deposit. + deposit_id TEXT NOT NULL REFERENCES asset_deposits(deposit_id), + + -- outpoint is the outpoint of the UTXO that was leased. + outpoint TEXT NOT NULL +); diff --git a/loopdb/sqlc/models.go b/loopdb/sqlc/models.go index 6728bb420..fd7f18dd2 100644 --- a/loopdb/sqlc/models.go +++ b/loopdb/sqlc/models.go @@ -9,6 +9,40 @@ import ( "time" ) +type AssetDeposit struct { + DepositID string + ProtocolVersion int32 + CreatedAt time.Time + AssetID []byte + Amount int64 + ClientScriptPubkey []byte + ServerScriptPubkey []byte + ClientInternalPubkey []byte + ServerInternalPubkey []byte + ServerInternalKey []byte + Expiry int32 + ClientKeyFamily int32 + ClientKeyIndex int32 + Addr string + ConfirmationHeight sql.NullInt32 + Outpoint sql.NullString + PkScript []byte + SweepAddr sql.NullString +} + +type AssetDepositLeasedUtxo struct { + ID int32 + DepositID string + Outpoint string +} + +type AssetDepositUpdate struct { + ID int32 + DepositID string + UpdateState int32 + UpdateTimestamp time.Time +} + type Deposit struct { ID int32 DepositID []byte diff --git a/loopdb/sqlc/querier.go b/loopdb/sqlc/querier.go index 15b2e388f..2d0a7e043 100644 --- a/loopdb/sqlc/querier.go +++ b/loopdb/sqlc/querier.go @@ -10,6 +10,7 @@ import ( ) type Querier interface { + AddAssetDeposit(ctx context.Context, arg AddAssetDepositParams) error AllDeposits(ctx context.Context) ([]Deposit, error) AllStaticAddresses(ctx context.Context) ([]StaticAddress, error) CancelBatch(ctx context.Context, id int32) error @@ -19,7 +20,10 @@ type Querier interface { CreateWithdrawal(ctx context.Context, arg CreateWithdrawalParams) error CreateWithdrawalDeposit(ctx context.Context, arg CreateWithdrawalDepositParams) error FetchLiquidityParams(ctx context.Context) ([]byte, error) + GetActiveAssetDeposits(ctx context.Context) ([]GetActiveAssetDepositsRow, error) GetAllWithdrawals(ctx context.Context) ([]Withdrawal, error) + GetAssetDepositServerInternalKey(ctx context.Context, depositID string) ([]byte, error) + GetAssetDeposits(ctx context.Context) ([]GetAssetDepositsRow, error) GetBatchSweeps(ctx context.Context, batchID int32) ([]Sweep, error) GetBatchSweptAmount(ctx context.Context, batchID int32) (int64, error) GetDeposit(ctx context.Context, depositID []byte) (Deposit, error) @@ -61,9 +65,13 @@ type Querier interface { InsertSwap(ctx context.Context, arg InsertSwapParams) error InsertSwapUpdate(ctx context.Context, arg InsertSwapUpdateParams) error IsStored(ctx context.Context, swapHash []byte) (bool, error) + MarkDepositConfirmed(ctx context.Context, arg MarkDepositConfirmedParams) error OverrideSwapCosts(ctx context.Context, arg OverrideSwapCostsParams) error + SetAssetDepositServerInternalKey(ctx context.Context, arg SetAssetDepositServerInternalKeyParams) error + SetAssetDepositSweepAddr(ctx context.Context, arg SetAssetDepositSweepAddrParams) error UpdateBatch(ctx context.Context, arg UpdateBatchParams) error UpdateDeposit(ctx context.Context, arg UpdateDepositParams) error + UpdateDepositState(ctx context.Context, arg UpdateDepositStateParams) error UpdateInstantOut(ctx context.Context, arg UpdateInstantOutParams) error UpdateLoopOutAssetOffchainPayments(ctx context.Context, arg UpdateLoopOutAssetOffchainPaymentsParams) error UpdateReservation(ctx context.Context, arg UpdateReservationParams) error diff --git a/loopdb/sqlc/queries/asset_deposits.sql b/loopdb/sqlc/queries/asset_deposits.sql new file mode 100644 index 000000000..1d761a7e7 --- /dev/null +++ b/loopdb/sqlc/queries/asset_deposits.sql @@ -0,0 +1,71 @@ +-- name: AddAssetDeposit :exec +INSERT INTO asset_deposits ( + deposit_id, + protocol_version, + created_at, + asset_id, + amount, + client_script_pubkey, + server_script_pubkey, + client_internal_pubkey, + server_internal_pubkey, + server_internal_key, + client_key_family, + client_key_index, + expiry, + addr +) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14); + +-- name: UpdateDepositState :exec +INSERT INTO asset_deposit_updates ( + deposit_id, + update_state, + update_timestamp +) VALUES ($1, $2, $3); + +-- name: MarkDepositConfirmed :exec +UPDATE asset_deposits +SET confirmation_height = $2, outpoint = $3, pk_script = $4 +WHERE deposit_id = $1; + +-- name: GetAssetDeposits :many +SELECT d.*, u.update_state, u.update_timestamp +FROM asset_deposits d +JOIN asset_deposit_updates u ON u.id = ( + SELECT id + FROM asset_deposit_updates + WHERE deposit_id = d.deposit_id + ORDER BY update_timestamp DESC + LIMIT 1 +) +ORDER BY d.created_at ASC; + +-- name: SetAssetDepositSweepAddr :exec +UPDATE asset_deposits +SET sweep_addr = $2 +WHERE deposit_id = $1; + +-- name: GetActiveAssetDeposits :many +SELECT d.*, u.update_state, u.update_timestamp +FROM asset_deposits d +JOIN asset_deposit_updates u + ON u.deposit_id = d.deposit_id +WHERE u.id = ( + SELECT id + FROM asset_deposit_updates + WHERE deposit_id = d.deposit_id + ORDER BY update_timestamp DESC + LIMIT 1 +) +AND u.update_state IN (0, 1, 2, 3, 4, 5, 6); + +-- name: SetAssetDepositServerInternalKey :exec +UPDATE asset_deposits +SET server_internal_key = $2 +WHERE deposit_id = $1 +AND server_internal_key IS NULL; + +-- name: GetAssetDepositServerInternalKey :one +SELECT server_internal_key +FROM asset_deposits +WHERE deposit_id = $1; diff --git a/looprpc/client_asset_deposit.pb.go b/looprpc/client_asset_deposit.pb.go new file mode 100644 index 000000000..139e08019 --- /dev/null +++ b/looprpc/client_asset_deposit.pb.go @@ -0,0 +1,921 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v3.21.12 +// source: client_asset_deposit.proto + +package looprpc + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type NewAssetDepositRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AssetId string `protobuf:"bytes,1,opt,name=asset_id,json=assetId,proto3" json:"asset_id,omitempty"` + Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` + CsvExpiry int32 `protobuf:"varint,3,opt,name=csv_expiry,json=csvExpiry,proto3" json:"csv_expiry,omitempty"` +} + +func (x *NewAssetDepositRequest) Reset() { + *x = NewAssetDepositRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_client_asset_deposit_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NewAssetDepositRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NewAssetDepositRequest) ProtoMessage() {} + +func (x *NewAssetDepositRequest) ProtoReflect() protoreflect.Message { + mi := &file_client_asset_deposit_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NewAssetDepositRequest.ProtoReflect.Descriptor instead. +func (*NewAssetDepositRequest) Descriptor() ([]byte, []int) { + return file_client_asset_deposit_proto_rawDescGZIP(), []int{0} +} + +func (x *NewAssetDepositRequest) GetAssetId() string { + if x != nil { + return x.AssetId + } + return "" +} + +func (x *NewAssetDepositRequest) GetAmount() uint64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *NewAssetDepositRequest) GetCsvExpiry() int32 { + if x != nil { + return x.CsvExpiry + } + return 0 +} + +type NewAssetDepositResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DepositId string `protobuf:"bytes,1,opt,name=deposit_id,json=depositId,proto3" json:"deposit_id,omitempty"` +} + +func (x *NewAssetDepositResponse) Reset() { + *x = NewAssetDepositResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_client_asset_deposit_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NewAssetDepositResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NewAssetDepositResponse) ProtoMessage() {} + +func (x *NewAssetDepositResponse) ProtoReflect() protoreflect.Message { + mi := &file_client_asset_deposit_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NewAssetDepositResponse.ProtoReflect.Descriptor instead. +func (*NewAssetDepositResponse) Descriptor() ([]byte, []int) { + return file_client_asset_deposit_proto_rawDescGZIP(), []int{1} +} + +func (x *NewAssetDepositResponse) GetDepositId() string { + if x != nil { + return x.DepositId + } + return "" +} + +type ListAssetDepositsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The number of minimum confirmations a deposit anchor must have to be + // listed. + MinConfs uint32 `protobuf:"varint,1,opt,name=min_confs,json=minConfs,proto3" json:"min_confs,omitempty"` + // The number of maximum confirmations a deposit anchor may have to be + // listed. A zero value indicates that there is no maximum. + MaxConfs uint32 `protobuf:"varint,2,opt,name=max_confs,json=maxConfs,proto3" json:"max_confs,omitempty"` +} + +func (x *ListAssetDepositsRequest) Reset() { + *x = ListAssetDepositsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_client_asset_deposit_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListAssetDepositsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAssetDepositsRequest) ProtoMessage() {} + +func (x *ListAssetDepositsRequest) ProtoReflect() protoreflect.Message { + mi := &file_client_asset_deposit_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAssetDepositsRequest.ProtoReflect.Descriptor instead. +func (*ListAssetDepositsRequest) Descriptor() ([]byte, []int) { + return file_client_asset_deposit_proto_rawDescGZIP(), []int{2} +} + +func (x *ListAssetDepositsRequest) GetMinConfs() uint32 { + if x != nil { + return x.MinConfs + } + return 0 +} + +func (x *ListAssetDepositsRequest) GetMaxConfs() uint32 { + if x != nil { + return x.MaxConfs + } + return 0 +} + +type ListAssetDepositsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // A list of all deposits that match the filtered state. + FilteredDeposits []*AssetDeposit `protobuf:"bytes,1,rep,name=filtered_deposits,json=filteredDeposits,proto3" json:"filtered_deposits,omitempty"` +} + +func (x *ListAssetDepositsResponse) Reset() { + *x = ListAssetDepositsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_client_asset_deposit_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListAssetDepositsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAssetDepositsResponse) ProtoMessage() {} + +func (x *ListAssetDepositsResponse) ProtoReflect() protoreflect.Message { + mi := &file_client_asset_deposit_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAssetDepositsResponse.ProtoReflect.Descriptor instead. +func (*ListAssetDepositsResponse) Descriptor() ([]byte, []int) { + return file_client_asset_deposit_proto_rawDescGZIP(), []int{3} +} + +func (x *ListAssetDepositsResponse) GetFilteredDeposits() []*AssetDeposit { + if x != nil { + return x.FilteredDeposits + } + return nil +} + +type AssetDeposit struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DepositId string `protobuf:"bytes,1,opt,name=deposit_id,json=depositId,proto3" json:"deposit_id,omitempty"` + CreatedAt int64 `protobuf:"varint,2,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + AssetId string `protobuf:"bytes,3,opt,name=asset_id,json=assetId,proto3" json:"asset_id,omitempty"` + Amount uint64 `protobuf:"varint,4,opt,name=amount,proto3" json:"amount,omitempty"` + DepositAddr string `protobuf:"bytes,5,opt,name=deposit_addr,json=depositAddr,proto3" json:"deposit_addr,omitempty"` + State string `protobuf:"bytes,6,opt,name=state,proto3" json:"state,omitempty"` + AnchorOutpoint string `protobuf:"bytes,7,opt,name=anchor_outpoint,json=anchorOutpoint,proto3" json:"anchor_outpoint,omitempty"` + ConfirmationHeight uint32 `protobuf:"varint,8,opt,name=confirmation_height,json=confirmationHeight,proto3" json:"confirmation_height,omitempty"` + Expiry uint32 `protobuf:"varint,9,opt,name=expiry,proto3" json:"expiry,omitempty"` + SweepAddr string `protobuf:"bytes,10,opt,name=sweep_addr,json=sweepAddr,proto3" json:"sweep_addr,omitempty"` +} + +func (x *AssetDeposit) Reset() { + *x = AssetDeposit{} + if protoimpl.UnsafeEnabled { + mi := &file_client_asset_deposit_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AssetDeposit) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AssetDeposit) ProtoMessage() {} + +func (x *AssetDeposit) ProtoReflect() protoreflect.Message { + mi := &file_client_asset_deposit_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AssetDeposit.ProtoReflect.Descriptor instead. +func (*AssetDeposit) Descriptor() ([]byte, []int) { + return file_client_asset_deposit_proto_rawDescGZIP(), []int{4} +} + +func (x *AssetDeposit) GetDepositId() string { + if x != nil { + return x.DepositId + } + return "" +} + +func (x *AssetDeposit) GetCreatedAt() int64 { + if x != nil { + return x.CreatedAt + } + return 0 +} + +func (x *AssetDeposit) GetAssetId() string { + if x != nil { + return x.AssetId + } + return "" +} + +func (x *AssetDeposit) GetAmount() uint64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *AssetDeposit) GetDepositAddr() string { + if x != nil { + return x.DepositAddr + } + return "" +} + +func (x *AssetDeposit) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *AssetDeposit) GetAnchorOutpoint() string { + if x != nil { + return x.AnchorOutpoint + } + return "" +} + +func (x *AssetDeposit) GetConfirmationHeight() uint32 { + if x != nil { + return x.ConfirmationHeight + } + return 0 +} + +func (x *AssetDeposit) GetExpiry() uint32 { + if x != nil { + return x.Expiry + } + return 0 +} + +func (x *AssetDeposit) GetSweepAddr() string { + if x != nil { + return x.SweepAddr + } + return "" +} + +type WithdrawAssetDepositsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DepositIds []string `protobuf:"bytes,1,rep,name=deposit_ids,json=depositIds,proto3" json:"deposit_ids,omitempty"` +} + +func (x *WithdrawAssetDepositsRequest) Reset() { + *x = WithdrawAssetDepositsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_client_asset_deposit_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WithdrawAssetDepositsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WithdrawAssetDepositsRequest) ProtoMessage() {} + +func (x *WithdrawAssetDepositsRequest) ProtoReflect() protoreflect.Message { + mi := &file_client_asset_deposit_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WithdrawAssetDepositsRequest.ProtoReflect.Descriptor instead. +func (*WithdrawAssetDepositsRequest) Descriptor() ([]byte, []int) { + return file_client_asset_deposit_proto_rawDescGZIP(), []int{5} +} + +func (x *WithdrawAssetDepositsRequest) GetDepositIds() []string { + if x != nil { + return x.DepositIds + } + return nil +} + +type WithdrawAssetDepositsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *WithdrawAssetDepositsResponse) Reset() { + *x = WithdrawAssetDepositsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_client_asset_deposit_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WithdrawAssetDepositsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WithdrawAssetDepositsResponse) ProtoMessage() {} + +func (x *WithdrawAssetDepositsResponse) ProtoReflect() protoreflect.Message { + mi := &file_client_asset_deposit_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WithdrawAssetDepositsResponse.ProtoReflect.Descriptor instead. +func (*WithdrawAssetDepositsResponse) Descriptor() ([]byte, []int) { + return file_client_asset_deposit_proto_rawDescGZIP(), []int{6} +} + +type RevealAssetDepositKeyRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DepositId string `protobuf:"bytes,1,opt,name=deposit_id,json=depositId,proto3" json:"deposit_id,omitempty"` +} + +func (x *RevealAssetDepositKeyRequest) Reset() { + *x = RevealAssetDepositKeyRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_client_asset_deposit_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RevealAssetDepositKeyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevealAssetDepositKeyRequest) ProtoMessage() {} + +func (x *RevealAssetDepositKeyRequest) ProtoReflect() protoreflect.Message { + mi := &file_client_asset_deposit_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RevealAssetDepositKeyRequest.ProtoReflect.Descriptor instead. +func (*RevealAssetDepositKeyRequest) Descriptor() ([]byte, []int) { + return file_client_asset_deposit_proto_rawDescGZIP(), []int{7} +} + +func (x *RevealAssetDepositKeyRequest) GetDepositId() string { + if x != nil { + return x.DepositId + } + return "" +} + +type RevealAssetDepositKeyResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *RevealAssetDepositKeyResponse) Reset() { + *x = RevealAssetDepositKeyResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_client_asset_deposit_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RevealAssetDepositKeyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevealAssetDepositKeyResponse) ProtoMessage() {} + +func (x *RevealAssetDepositKeyResponse) ProtoReflect() protoreflect.Message { + mi := &file_client_asset_deposit_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RevealAssetDepositKeyResponse.ProtoReflect.Descriptor instead. +func (*RevealAssetDepositKeyResponse) Descriptor() ([]byte, []int) { + return file_client_asset_deposit_proto_rawDescGZIP(), []int{8} +} + +type TestCoSignAssetDepositHTLCRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DepositId string `protobuf:"bytes,1,opt,name=deposit_id,json=depositId,proto3" json:"deposit_id,omitempty"` +} + +func (x *TestCoSignAssetDepositHTLCRequest) Reset() { + *x = TestCoSignAssetDepositHTLCRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_client_asset_deposit_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TestCoSignAssetDepositHTLCRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestCoSignAssetDepositHTLCRequest) ProtoMessage() {} + +func (x *TestCoSignAssetDepositHTLCRequest) ProtoReflect() protoreflect.Message { + mi := &file_client_asset_deposit_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TestCoSignAssetDepositHTLCRequest.ProtoReflect.Descriptor instead. +func (*TestCoSignAssetDepositHTLCRequest) Descriptor() ([]byte, []int) { + return file_client_asset_deposit_proto_rawDescGZIP(), []int{9} +} + +func (x *TestCoSignAssetDepositHTLCRequest) GetDepositId() string { + if x != nil { + return x.DepositId + } + return "" +} + +type TestCoSignAssetDepositHTLCResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *TestCoSignAssetDepositHTLCResponse) Reset() { + *x = TestCoSignAssetDepositHTLCResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_client_asset_deposit_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TestCoSignAssetDepositHTLCResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestCoSignAssetDepositHTLCResponse) ProtoMessage() {} + +func (x *TestCoSignAssetDepositHTLCResponse) ProtoReflect() protoreflect.Message { + mi := &file_client_asset_deposit_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TestCoSignAssetDepositHTLCResponse.ProtoReflect.Descriptor instead. +func (*TestCoSignAssetDepositHTLCResponse) Descriptor() ([]byte, []int) { + return file_client_asset_deposit_proto_rawDescGZIP(), []int{10} +} + +var File_client_asset_deposit_proto protoreflect.FileDescriptor + +var file_client_asset_deposit_proto_rawDesc = []byte{ + 0x0a, 0x1a, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x64, + 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x6c, 0x6f, + 0x6f, 0x70, 0x72, 0x70, 0x63, 0x22, 0x6a, 0x0a, 0x16, 0x4e, 0x65, 0x77, 0x41, 0x73, 0x73, 0x65, + 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x19, 0x0a, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x61, 0x73, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x73, 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x63, 0x73, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, + 0x79, 0x22, 0x38, 0x0a, 0x17, 0x4e, 0x65, 0x77, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, + 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x49, 0x64, 0x22, 0x54, 0x0a, 0x18, 0x4c, + 0x69, 0x73, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, + 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, + 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x6e, 0x66, + 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x66, + 0x73, 0x22, 0x5f, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, + 0x0a, 0x11, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x64, 0x65, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, + 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, + 0x52, 0x10, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x65, 0x64, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x73, 0x22, 0xc9, 0x02, 0x0a, 0x0c, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, + 0x73, 0x69, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, + 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x73, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x5f, + 0x61, 0x64, 0x64, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x70, 0x6f, + 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, + 0x0f, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x4f, 0x75, + 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2f, 0x0a, 0x13, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, + 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, + 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, + 0x1d, 0x0a, 0x0a, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x77, 0x65, 0x65, 0x70, 0x41, 0x64, 0x64, 0x72, 0x22, 0x3f, + 0x0a, 0x1c, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, + 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, + 0x0a, 0x0b, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x49, 0x64, 0x73, 0x22, + 0x1f, 0x0a, 0x1d, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x41, 0x73, 0x73, 0x65, 0x74, + 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x3d, 0x0a, 0x1c, 0x52, 0x65, 0x76, 0x65, 0x61, 0x6c, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, + 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x49, 0x64, 0x22, + 0x1f, 0x0a, 0x1d, 0x52, 0x65, 0x76, 0x65, 0x61, 0x6c, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x42, 0x0a, 0x21, 0x54, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x53, 0x69, 0x67, 0x6e, 0x41, 0x73, + 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x48, 0x54, 0x4c, 0x43, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x65, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x49, 0x64, 0x22, 0x24, 0x0a, 0x22, 0x54, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x53, 0x69, + 0x67, 0x6e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x48, 0x54, + 0x4c, 0x43, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x8d, 0x04, 0x0a, 0x12, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x12, 0x54, 0x0a, 0x0f, 0x4e, 0x65, 0x77, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x12, 0x1f, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4e, + 0x65, 0x77, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, + 0x4e, 0x65, 0x77, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x73, 0x12, 0x21, 0x2e, 0x6c, + 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, + 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x73, + 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x15, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x73, 0x12, 0x25, 0x2e, 0x6c, + 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, + 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x15, 0x52, + 0x65, 0x76, 0x65, 0x61, 0x6c, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x4b, 0x65, 0x79, 0x12, 0x25, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, + 0x65, 0x76, 0x65, 0x61, 0x6c, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6c, 0x6f, + 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x65, 0x61, 0x6c, 0x41, 0x73, 0x73, 0x65, + 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x75, 0x0a, 0x1a, 0x54, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x53, 0x69, 0x67, + 0x6e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x48, 0x54, 0x4c, + 0x43, 0x12, 0x2a, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x73, 0x74, + 0x43, 0x6f, 0x53, 0x69, 0x67, 0x6e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x48, 0x54, 0x4c, 0x43, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, + 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x53, 0x69, + 0x67, 0x6e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x48, 0x54, + 0x4c, 0x43, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, + 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, 0x6c, 0x6f, 0x6f, 0x70, + 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_client_asset_deposit_proto_rawDescOnce sync.Once + file_client_asset_deposit_proto_rawDescData = file_client_asset_deposit_proto_rawDesc +) + +func file_client_asset_deposit_proto_rawDescGZIP() []byte { + file_client_asset_deposit_proto_rawDescOnce.Do(func() { + file_client_asset_deposit_proto_rawDescData = protoimpl.X.CompressGZIP(file_client_asset_deposit_proto_rawDescData) + }) + return file_client_asset_deposit_proto_rawDescData +} + +var file_client_asset_deposit_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_client_asset_deposit_proto_goTypes = []any{ + (*NewAssetDepositRequest)(nil), // 0: looprpc.NewAssetDepositRequest + (*NewAssetDepositResponse)(nil), // 1: looprpc.NewAssetDepositResponse + (*ListAssetDepositsRequest)(nil), // 2: looprpc.ListAssetDepositsRequest + (*ListAssetDepositsResponse)(nil), // 3: looprpc.ListAssetDepositsResponse + (*AssetDeposit)(nil), // 4: looprpc.AssetDeposit + (*WithdrawAssetDepositsRequest)(nil), // 5: looprpc.WithdrawAssetDepositsRequest + (*WithdrawAssetDepositsResponse)(nil), // 6: looprpc.WithdrawAssetDepositsResponse + (*RevealAssetDepositKeyRequest)(nil), // 7: looprpc.RevealAssetDepositKeyRequest + (*RevealAssetDepositKeyResponse)(nil), // 8: looprpc.RevealAssetDepositKeyResponse + (*TestCoSignAssetDepositHTLCRequest)(nil), // 9: looprpc.TestCoSignAssetDepositHTLCRequest + (*TestCoSignAssetDepositHTLCResponse)(nil), // 10: looprpc.TestCoSignAssetDepositHTLCResponse +} +var file_client_asset_deposit_proto_depIdxs = []int32{ + 4, // 0: looprpc.ListAssetDepositsResponse.filtered_deposits:type_name -> looprpc.AssetDeposit + 0, // 1: looprpc.AssetDepositClient.NewAssetDeposit:input_type -> looprpc.NewAssetDepositRequest + 2, // 2: looprpc.AssetDepositClient.ListAssetDeposits:input_type -> looprpc.ListAssetDepositsRequest + 5, // 3: looprpc.AssetDepositClient.WithdrawAssetDeposits:input_type -> looprpc.WithdrawAssetDepositsRequest + 7, // 4: looprpc.AssetDepositClient.RevealAssetDepositKey:input_type -> looprpc.RevealAssetDepositKeyRequest + 9, // 5: looprpc.AssetDepositClient.TestCoSignAssetDepositHTLC:input_type -> looprpc.TestCoSignAssetDepositHTLCRequest + 1, // 6: looprpc.AssetDepositClient.NewAssetDeposit:output_type -> looprpc.NewAssetDepositResponse + 3, // 7: looprpc.AssetDepositClient.ListAssetDeposits:output_type -> looprpc.ListAssetDepositsResponse + 6, // 8: looprpc.AssetDepositClient.WithdrawAssetDeposits:output_type -> looprpc.WithdrawAssetDepositsResponse + 8, // 9: looprpc.AssetDepositClient.RevealAssetDepositKey:output_type -> looprpc.RevealAssetDepositKeyResponse + 10, // 10: looprpc.AssetDepositClient.TestCoSignAssetDepositHTLC:output_type -> looprpc.TestCoSignAssetDepositHTLCResponse + 6, // [6:11] is the sub-list for method output_type + 1, // [1:6] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_client_asset_deposit_proto_init() } +func file_client_asset_deposit_proto_init() { + if File_client_asset_deposit_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_client_asset_deposit_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*NewAssetDepositRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_client_asset_deposit_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*NewAssetDepositResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_client_asset_deposit_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*ListAssetDepositsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_client_asset_deposit_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*ListAssetDepositsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_client_asset_deposit_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*AssetDeposit); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_client_asset_deposit_proto_msgTypes[5].Exporter = func(v any, i int) any { + switch v := v.(*WithdrawAssetDepositsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_client_asset_deposit_proto_msgTypes[6].Exporter = func(v any, i int) any { + switch v := v.(*WithdrawAssetDepositsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_client_asset_deposit_proto_msgTypes[7].Exporter = func(v any, i int) any { + switch v := v.(*RevealAssetDepositKeyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_client_asset_deposit_proto_msgTypes[8].Exporter = func(v any, i int) any { + switch v := v.(*RevealAssetDepositKeyResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_client_asset_deposit_proto_msgTypes[9].Exporter = func(v any, i int) any { + switch v := v.(*TestCoSignAssetDepositHTLCRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_client_asset_deposit_proto_msgTypes[10].Exporter = func(v any, i int) any { + switch v := v.(*TestCoSignAssetDepositHTLCResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_client_asset_deposit_proto_rawDesc, + NumEnums: 0, + NumMessages: 11, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_client_asset_deposit_proto_goTypes, + DependencyIndexes: file_client_asset_deposit_proto_depIdxs, + MessageInfos: file_client_asset_deposit_proto_msgTypes, + }.Build() + File_client_asset_deposit_proto = out.File + file_client_asset_deposit_proto_rawDesc = nil + file_client_asset_deposit_proto_goTypes = nil + file_client_asset_deposit_proto_depIdxs = nil +} diff --git a/looprpc/client_asset_deposit.proto b/looprpc/client_asset_deposit.proto new file mode 100644 index 000000000..b86caaef0 --- /dev/null +++ b/looprpc/client_asset_deposit.proto @@ -0,0 +1,92 @@ +syntax = "proto3"; + +package looprpc; + +option go_package = "github.com/lightninglabs/loop/looprpc"; + +service AssetDepositClient { + rpc NewAssetDeposit (NewAssetDepositRequest) + returns (NewAssetDepositResponse); + + rpc ListAssetDeposits (ListAssetDepositsRequest) + returns (ListAssetDepositsResponse); + + rpc WithdrawAssetDeposits (WithdrawAssetDepositsRequest) + returns (WithdrawAssetDepositsResponse); + + rpc RevealAssetDepositKey (RevealAssetDepositKeyRequest) + returns (RevealAssetDepositKeyResponse); + + rpc TestCoSignAssetDepositHTLC (TestCoSignAssetDepositHTLCRequest) + returns (TestCoSignAssetDepositHTLCResponse); +} + +message NewAssetDepositRequest { + string asset_id = 1; + + uint64 amount = 2; + + int32 csv_expiry = 3; +} + +message NewAssetDepositResponse { + string deposit_id = 1; +} + +message ListAssetDepositsRequest { + // The number of minimum confirmations a deposit anchor must have to be + // listed. + uint32 min_confs = 1; + + // The number of maximum confirmations a deposit anchor may have to be + // listed. A zero value indicates that there is no maximum. + uint32 max_confs = 2; +} + +message ListAssetDepositsResponse { + // A list of all deposits that match the filtered state. + repeated AssetDeposit filtered_deposits = 1; +} + +message AssetDeposit { + string deposit_id = 1; + + int64 created_at = 2; + + string asset_id = 3; + + uint64 amount = 4; + + string deposit_addr = 5; + + string state = 6; + + string anchor_outpoint = 7; + + uint32 confirmation_height = 8; + + uint32 expiry = 9; + + string sweep_addr = 10; +} + +message WithdrawAssetDepositsRequest { + repeated string deposit_ids = 1; +} + +message WithdrawAssetDepositsResponse { +} + +message RevealAssetDepositKeyRequest { + string deposit_id = 1; +} + +message RevealAssetDepositKeyResponse { +} + +message TestCoSignAssetDepositHTLCRequest { + string deposit_id = 1; +} + +message TestCoSignAssetDepositHTLCResponse { +} diff --git a/looprpc/client_asset_deposit_grpc.pb.go b/looprpc/client_asset_deposit_grpc.pb.go new file mode 100644 index 000000000..0101d6b75 --- /dev/null +++ b/looprpc/client_asset_deposit_grpc.pb.go @@ -0,0 +1,245 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package looprpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// AssetDepositClientClient is the client API for AssetDepositClient service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AssetDepositClientClient interface { + NewAssetDeposit(ctx context.Context, in *NewAssetDepositRequest, opts ...grpc.CallOption) (*NewAssetDepositResponse, error) + ListAssetDeposits(ctx context.Context, in *ListAssetDepositsRequest, opts ...grpc.CallOption) (*ListAssetDepositsResponse, error) + WithdrawAssetDeposits(ctx context.Context, in *WithdrawAssetDepositsRequest, opts ...grpc.CallOption) (*WithdrawAssetDepositsResponse, error) + RevealAssetDepositKey(ctx context.Context, in *RevealAssetDepositKeyRequest, opts ...grpc.CallOption) (*RevealAssetDepositKeyResponse, error) + TestCoSignAssetDepositHTLC(ctx context.Context, in *TestCoSignAssetDepositHTLCRequest, opts ...grpc.CallOption) (*TestCoSignAssetDepositHTLCResponse, error) +} + +type assetDepositClientClient struct { + cc grpc.ClientConnInterface +} + +func NewAssetDepositClientClient(cc grpc.ClientConnInterface) AssetDepositClientClient { + return &assetDepositClientClient{cc} +} + +func (c *assetDepositClientClient) NewAssetDeposit(ctx context.Context, in *NewAssetDepositRequest, opts ...grpc.CallOption) (*NewAssetDepositResponse, error) { + out := new(NewAssetDepositResponse) + err := c.cc.Invoke(ctx, "/looprpc.AssetDepositClient/NewAssetDeposit", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *assetDepositClientClient) ListAssetDeposits(ctx context.Context, in *ListAssetDepositsRequest, opts ...grpc.CallOption) (*ListAssetDepositsResponse, error) { + out := new(ListAssetDepositsResponse) + err := c.cc.Invoke(ctx, "/looprpc.AssetDepositClient/ListAssetDeposits", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *assetDepositClientClient) WithdrawAssetDeposits(ctx context.Context, in *WithdrawAssetDepositsRequest, opts ...grpc.CallOption) (*WithdrawAssetDepositsResponse, error) { + out := new(WithdrawAssetDepositsResponse) + err := c.cc.Invoke(ctx, "/looprpc.AssetDepositClient/WithdrawAssetDeposits", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *assetDepositClientClient) RevealAssetDepositKey(ctx context.Context, in *RevealAssetDepositKeyRequest, opts ...grpc.CallOption) (*RevealAssetDepositKeyResponse, error) { + out := new(RevealAssetDepositKeyResponse) + err := c.cc.Invoke(ctx, "/looprpc.AssetDepositClient/RevealAssetDepositKey", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *assetDepositClientClient) TestCoSignAssetDepositHTLC(ctx context.Context, in *TestCoSignAssetDepositHTLCRequest, opts ...grpc.CallOption) (*TestCoSignAssetDepositHTLCResponse, error) { + out := new(TestCoSignAssetDepositHTLCResponse) + err := c.cc.Invoke(ctx, "/looprpc.AssetDepositClient/TestCoSignAssetDepositHTLC", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AssetDepositClientServer is the server API for AssetDepositClient service. +// All implementations must embed UnimplementedAssetDepositClientServer +// for forward compatibility +type AssetDepositClientServer interface { + NewAssetDeposit(context.Context, *NewAssetDepositRequest) (*NewAssetDepositResponse, error) + ListAssetDeposits(context.Context, *ListAssetDepositsRequest) (*ListAssetDepositsResponse, error) + WithdrawAssetDeposits(context.Context, *WithdrawAssetDepositsRequest) (*WithdrawAssetDepositsResponse, error) + RevealAssetDepositKey(context.Context, *RevealAssetDepositKeyRequest) (*RevealAssetDepositKeyResponse, error) + TestCoSignAssetDepositHTLC(context.Context, *TestCoSignAssetDepositHTLCRequest) (*TestCoSignAssetDepositHTLCResponse, error) + mustEmbedUnimplementedAssetDepositClientServer() +} + +// UnimplementedAssetDepositClientServer must be embedded to have forward compatible implementations. +type UnimplementedAssetDepositClientServer struct { +} + +func (UnimplementedAssetDepositClientServer) NewAssetDeposit(context.Context, *NewAssetDepositRequest) (*NewAssetDepositResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method NewAssetDeposit not implemented") +} +func (UnimplementedAssetDepositClientServer) ListAssetDeposits(context.Context, *ListAssetDepositsRequest) (*ListAssetDepositsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListAssetDeposits not implemented") +} +func (UnimplementedAssetDepositClientServer) WithdrawAssetDeposits(context.Context, *WithdrawAssetDepositsRequest) (*WithdrawAssetDepositsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method WithdrawAssetDeposits not implemented") +} +func (UnimplementedAssetDepositClientServer) RevealAssetDepositKey(context.Context, *RevealAssetDepositKeyRequest) (*RevealAssetDepositKeyResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RevealAssetDepositKey not implemented") +} +func (UnimplementedAssetDepositClientServer) TestCoSignAssetDepositHTLC(context.Context, *TestCoSignAssetDepositHTLCRequest) (*TestCoSignAssetDepositHTLCResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method TestCoSignAssetDepositHTLC not implemented") +} +func (UnimplementedAssetDepositClientServer) mustEmbedUnimplementedAssetDepositClientServer() {} + +// UnsafeAssetDepositClientServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AssetDepositClientServer will +// result in compilation errors. +type UnsafeAssetDepositClientServer interface { + mustEmbedUnimplementedAssetDepositClientServer() +} + +func RegisterAssetDepositClientServer(s grpc.ServiceRegistrar, srv AssetDepositClientServer) { + s.RegisterService(&AssetDepositClient_ServiceDesc, srv) +} + +func _AssetDepositClient_NewAssetDeposit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(NewAssetDepositRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetDepositClientServer).NewAssetDeposit(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetDepositClient/NewAssetDeposit", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetDepositClientServer).NewAssetDeposit(ctx, req.(*NewAssetDepositRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AssetDepositClient_ListAssetDeposits_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListAssetDepositsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetDepositClientServer).ListAssetDeposits(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetDepositClient/ListAssetDeposits", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetDepositClientServer).ListAssetDeposits(ctx, req.(*ListAssetDepositsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AssetDepositClient_WithdrawAssetDeposits_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WithdrawAssetDepositsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetDepositClientServer).WithdrawAssetDeposits(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetDepositClient/WithdrawAssetDeposits", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetDepositClientServer).WithdrawAssetDeposits(ctx, req.(*WithdrawAssetDepositsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AssetDepositClient_RevealAssetDepositKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RevealAssetDepositKeyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetDepositClientServer).RevealAssetDepositKey(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetDepositClient/RevealAssetDepositKey", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetDepositClientServer).RevealAssetDepositKey(ctx, req.(*RevealAssetDepositKeyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AssetDepositClient_TestCoSignAssetDepositHTLC_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TestCoSignAssetDepositHTLCRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetDepositClientServer).TestCoSignAssetDepositHTLC(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetDepositClient/TestCoSignAssetDepositHTLC", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetDepositClientServer).TestCoSignAssetDepositHTLC(ctx, req.(*TestCoSignAssetDepositHTLCRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// AssetDepositClient_ServiceDesc is the grpc.ServiceDesc for AssetDepositClient service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var AssetDepositClient_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "looprpc.AssetDepositClient", + HandlerType: (*AssetDepositClientServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "NewAssetDeposit", + Handler: _AssetDepositClient_NewAssetDeposit_Handler, + }, + { + MethodName: "ListAssetDeposits", + Handler: _AssetDepositClient_ListAssetDeposits_Handler, + }, + { + MethodName: "WithdrawAssetDeposits", + Handler: _AssetDepositClient_WithdrawAssetDeposits_Handler, + }, + { + MethodName: "RevealAssetDepositKey", + Handler: _AssetDepositClient_RevealAssetDepositKey_Handler, + }, + { + MethodName: "TestCoSignAssetDepositHTLC", + Handler: _AssetDepositClient_TestCoSignAssetDepositHTLC_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "client_asset_deposit.proto", +} diff --git a/looprpc/perms.go b/looprpc/perms.go index be28d7c8d..573f5d4b6 100644 --- a/looprpc/perms.go +++ b/looprpc/perms.go @@ -176,4 +176,24 @@ var RequiredPermissions = map[string][]bakery.Op{ Entity: "swap", Action: "read", }}, + "/looprpc.AssetDepositClient/NewAssetDeposit": {{ + Entity: "swap", + Action: "execute", + }}, + "/looprpc.AssetDepositClient/ListAssetDeposits": {{ + Entity: "swap", + Action: "read", + }}, + "/looprpc.AssetDepositClient/WithdrawAssetDeposits": {{ + Entity: "swap", + Action: "read", + }}, + "/looprpc.AssetDepositClient/CoSignAssetDepositHTLC": {{ + Entity: "swap", + Action: "execute", + }}, + "/looprpc.AssetDepositClient/RevealAssetDepositKey": {{ + Entity: "swap", + Action: "execute", + }}, } diff --git a/swapserverrpc/asset_deposit.pb.go b/swapserverrpc/asset_deposit.pb.go new file mode 100644 index 000000000..7ff7886cd --- /dev/null +++ b/swapserverrpc/asset_deposit.pb.go @@ -0,0 +1,881 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v3.21.12 +// source: asset_deposit.proto + +// We can't change this to swapserverrpc, it would be a breaking change because +// the package name is also contained in the HTTP URIs and old clients would +// call the wrong endpoints. Luckily with the go_package option we can have +// different golang and RPC package names to fix protobuf namespace conflicts. + +package swapserverrpc + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// AssetDepositProtocolVersion is the version of the asset deposit protocol. +type AssetDepositProtocolVersion int32 + +const ( + // V0 is the first version of the asset deposit protocol. + AssetDepositProtocolVersion_ASSET_DEPOSIT_V0 AssetDepositProtocolVersion = 0 +) + +// Enum value maps for AssetDepositProtocolVersion. +var ( + AssetDepositProtocolVersion_name = map[int32]string{ + 0: "ASSET_DEPOSIT_V0", + } + AssetDepositProtocolVersion_value = map[string]int32{ + "ASSET_DEPOSIT_V0": 0, + } +) + +func (x AssetDepositProtocolVersion) Enum() *AssetDepositProtocolVersion { + p := new(AssetDepositProtocolVersion) + *p = x + return p +} + +func (x AssetDepositProtocolVersion) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (AssetDepositProtocolVersion) Descriptor() protoreflect.EnumDescriptor { + return file_asset_deposit_proto_enumTypes[0].Descriptor() +} + +func (AssetDepositProtocolVersion) Type() protoreflect.EnumType { + return &file_asset_deposit_proto_enumTypes[0] +} + +func (x AssetDepositProtocolVersion) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use AssetDepositProtocolVersion.Descriptor instead. +func (AssetDepositProtocolVersion) EnumDescriptor() ([]byte, []int) { + return file_asset_deposit_proto_rawDescGZIP(), []int{0} +} + +// NewAssetDepositServerReq is the request to the Server to create a new asset +// deposit. +type NewAssetDepositServerReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // asset_id is the id of the asset to deposit. + AssetId []byte `protobuf:"bytes,1,opt,name=asset_id,json=assetId,proto3" json:"asset_id,omitempty"` + // amount is the amount of the asset to deposit. + Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` + // client_internal_key is the client's internal pubkey used for the asset + // deposit deposit MuSig2 key. + ClientInternalPubkey []byte `protobuf:"bytes,3,opt,name=client_internal_pubkey,json=clientInternalPubkey,proto3" json:"client_internal_pubkey,omitempty"` + // client_script_key is the client's script pubkey used for the asset + // deposit timeout script. + ClientScriptPubkey []byte `protobuf:"bytes,4,opt,name=client_script_pubkey,json=clientScriptPubkey,proto3" json:"client_script_pubkey,omitempty"` + // csv_expiry is the CSV expiry for the deposit transaction. + CsvExpiry int32 `protobuf:"varint,5,opt,name=csv_expiry,json=csvExpiry,proto3" json:"csv_expiry,omitempty"` +} + +func (x *NewAssetDepositServerReq) Reset() { + *x = NewAssetDepositServerReq{} + if protoimpl.UnsafeEnabled { + mi := &file_asset_deposit_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NewAssetDepositServerReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NewAssetDepositServerReq) ProtoMessage() {} + +func (x *NewAssetDepositServerReq) ProtoReflect() protoreflect.Message { + mi := &file_asset_deposit_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NewAssetDepositServerReq.ProtoReflect.Descriptor instead. +func (*NewAssetDepositServerReq) Descriptor() ([]byte, []int) { + return file_asset_deposit_proto_rawDescGZIP(), []int{0} +} + +func (x *NewAssetDepositServerReq) GetAssetId() []byte { + if x != nil { + return x.AssetId + } + return nil +} + +func (x *NewAssetDepositServerReq) GetAmount() uint64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *NewAssetDepositServerReq) GetClientInternalPubkey() []byte { + if x != nil { + return x.ClientInternalPubkey + } + return nil +} + +func (x *NewAssetDepositServerReq) GetClientScriptPubkey() []byte { + if x != nil { + return x.ClientScriptPubkey + } + return nil +} + +func (x *NewAssetDepositServerReq) GetCsvExpiry() int32 { + if x != nil { + return x.CsvExpiry + } + return 0 +} + +// NewAssetDepositServerRes is the Server's response to a NewAssetDeposit +// request. +type NewAssetDepositServerRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // deposit_id is the unique id of the deposit. + DepositId string `protobuf:"bytes,1,opt,name=deposit_id,json=depositId,proto3" json:"deposit_id,omitempty"` + // server_script_pubkey is the script pubkey of the server used for the + // asset deposit spending HTLC script. + ServerScriptPubkey []byte `protobuf:"bytes,2,opt,name=server_script_pubkey,json=serverScriptPubkey,proto3" json:"server_script_pubkey,omitempty"` + // server_internal_pubkey is the public key of the server used for the asset + // deposit MuSig2 key. + ServerInternalPubkey []byte `protobuf:"bytes,3,opt,name=server_internal_pubkey,json=serverInternalPubkey,proto3" json:"server_internal_pubkey,omitempty"` + // deposit_addr is the TAP address to deposit the asset to. + DepositAddr string `protobuf:"bytes,4,opt,name=deposit_addr,json=depositAddr,proto3" json:"deposit_addr,omitempty"` +} + +func (x *NewAssetDepositServerRes) Reset() { + *x = NewAssetDepositServerRes{} + if protoimpl.UnsafeEnabled { + mi := &file_asset_deposit_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NewAssetDepositServerRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NewAssetDepositServerRes) ProtoMessage() {} + +func (x *NewAssetDepositServerRes) ProtoReflect() protoreflect.Message { + mi := &file_asset_deposit_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NewAssetDepositServerRes.ProtoReflect.Descriptor instead. +func (*NewAssetDepositServerRes) Descriptor() ([]byte, []int) { + return file_asset_deposit_proto_rawDescGZIP(), []int{1} +} + +func (x *NewAssetDepositServerRes) GetDepositId() string { + if x != nil { + return x.DepositId + } + return "" +} + +func (x *NewAssetDepositServerRes) GetServerScriptPubkey() []byte { + if x != nil { + return x.ServerScriptPubkey + } + return nil +} + +func (x *NewAssetDepositServerRes) GetServerInternalPubkey() []byte { + if x != nil { + return x.ServerInternalPubkey + } + return nil +} + +func (x *NewAssetDepositServerRes) GetDepositAddr() string { + if x != nil { + return x.DepositAddr + } + return "" +} + +type WithdrawAssetDepositsServerReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DepositIds []string `protobuf:"bytes,1,rep,name=deposit_ids,json=depositIds,proto3" json:"deposit_ids,omitempty"` +} + +func (x *WithdrawAssetDepositsServerReq) Reset() { + *x = WithdrawAssetDepositsServerReq{} + if protoimpl.UnsafeEnabled { + mi := &file_asset_deposit_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WithdrawAssetDepositsServerReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WithdrawAssetDepositsServerReq) ProtoMessage() {} + +func (x *WithdrawAssetDepositsServerReq) ProtoReflect() protoreflect.Message { + mi := &file_asset_deposit_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WithdrawAssetDepositsServerReq.ProtoReflect.Descriptor instead. +func (*WithdrawAssetDepositsServerReq) Descriptor() ([]byte, []int) { + return file_asset_deposit_proto_rawDescGZIP(), []int{2} +} + +func (x *WithdrawAssetDepositsServerReq) GetDepositIds() []string { + if x != nil { + return x.DepositIds + } + return nil +} + +type WithdrawAssetDepositsServerRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DepositKeys map[string][]byte `protobuf:"bytes,1,rep,name=deposit_keys,json=depositKeys,proto3" json:"deposit_keys,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *WithdrawAssetDepositsServerRes) Reset() { + *x = WithdrawAssetDepositsServerRes{} + if protoimpl.UnsafeEnabled { + mi := &file_asset_deposit_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WithdrawAssetDepositsServerRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WithdrawAssetDepositsServerRes) ProtoMessage() {} + +func (x *WithdrawAssetDepositsServerRes) ProtoReflect() protoreflect.Message { + mi := &file_asset_deposit_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WithdrawAssetDepositsServerRes.ProtoReflect.Descriptor instead. +func (*WithdrawAssetDepositsServerRes) Descriptor() ([]byte, []int) { + return file_asset_deposit_proto_rawDescGZIP(), []int{3} +} + +func (x *WithdrawAssetDepositsServerRes) GetDepositKeys() map[string][]byte { + if x != nil { + return x.DepositKeys + } + return nil +} + +// AssetDepositPartialSig holds a nonce and partial signature spending a +// deposit. +type AssetDepositPartialSig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // deposit_id is the deposit ID corresponding to this partial signature. + DepositId string `protobuf:"bytes,1,opt,name=deposit_id,json=depositId,proto3" json:"deposit_id,omitempty"` + // nonce is the nonce used for generating this signature. + Nonce []byte `protobuf:"bytes,2,opt,name=nonce,proto3" json:"nonce,omitempty"` + // partial_sig is the partial signature for spending the deposit. + PartialSig []byte `protobuf:"bytes,3,opt,name=partial_sig,json=partialSig,proto3" json:"partial_sig,omitempty"` +} + +func (x *AssetDepositPartialSig) Reset() { + *x = AssetDepositPartialSig{} + if protoimpl.UnsafeEnabled { + mi := &file_asset_deposit_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AssetDepositPartialSig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AssetDepositPartialSig) ProtoMessage() {} + +func (x *AssetDepositPartialSig) ProtoReflect() protoreflect.Message { + mi := &file_asset_deposit_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AssetDepositPartialSig.ProtoReflect.Descriptor instead. +func (*AssetDepositPartialSig) Descriptor() ([]byte, []int) { + return file_asset_deposit_proto_rawDescGZIP(), []int{4} +} + +func (x *AssetDepositPartialSig) GetDepositId() string { + if x != nil { + return x.DepositId + } + return "" +} + +func (x *AssetDepositPartialSig) GetNonce() []byte { + if x != nil { + return x.Nonce + } + return nil +} + +func (x *AssetDepositPartialSig) GetPartialSig() []byte { + if x != nil { + return x.PartialSig + } + return nil +} + +// PushAssetDepositHtlcSigsReq holds partial signatures spending one or more +// deposits and the zero fee HTLC spending them. +type PushAssetDepositHtlcSigsReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // partial_sigs holds the partial signatures for the deposits spent by the + // HTLC. The inputs of the HTLC will be in the same order defined here. + PartialSigs []*AssetDepositPartialSig `protobuf:"bytes,1,rep,name=partial_sigs,json=partialSigs,proto3" json:"partial_sigs,omitempty"` + // htlc_psbt is the HTLC psbt. + HtlcPsbt []byte `protobuf:"bytes,2,opt,name=htlc_psbt,json=htlcPsbt,proto3" json:"htlc_psbt,omitempty"` +} + +func (x *PushAssetDepositHtlcSigsReq) Reset() { + *x = PushAssetDepositHtlcSigsReq{} + if protoimpl.UnsafeEnabled { + mi := &file_asset_deposit_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushAssetDepositHtlcSigsReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushAssetDepositHtlcSigsReq) ProtoMessage() {} + +func (x *PushAssetDepositHtlcSigsReq) ProtoReflect() protoreflect.Message { + mi := &file_asset_deposit_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushAssetDepositHtlcSigsReq.ProtoReflect.Descriptor instead. +func (*PushAssetDepositHtlcSigsReq) Descriptor() ([]byte, []int) { + return file_asset_deposit_proto_rawDescGZIP(), []int{5} +} + +func (x *PushAssetDepositHtlcSigsReq) GetPartialSigs() []*AssetDepositPartialSig { + if x != nil { + return x.PartialSigs + } + return nil +} + +func (x *PushAssetDepositHtlcSigsReq) GetHtlcPsbt() []byte { + if x != nil { + return x.HtlcPsbt + } + return nil +} + +type PushAssetDepositHtlcSigsRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *PushAssetDepositHtlcSigsRes) Reset() { + *x = PushAssetDepositHtlcSigsRes{} + if protoimpl.UnsafeEnabled { + mi := &file_asset_deposit_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushAssetDepositHtlcSigsRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushAssetDepositHtlcSigsRes) ProtoMessage() {} + +func (x *PushAssetDepositHtlcSigsRes) ProtoReflect() protoreflect.Message { + mi := &file_asset_deposit_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushAssetDepositHtlcSigsRes.ProtoReflect.Descriptor instead. +func (*PushAssetDepositHtlcSigsRes) Descriptor() ([]byte, []int) { + return file_asset_deposit_proto_rawDescGZIP(), []int{6} +} + +// PushAssetDepositKeysReq holds private keys of one or more deposits. +type PushAssetDepositKeysReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // deposit_keys is a map wich maps deposit_id to deposit internal private + // key. + DepositKeys map[string][]byte `protobuf:"bytes,1,rep,name=deposit_keys,json=depositKeys,proto3" json:"deposit_keys,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *PushAssetDepositKeysReq) Reset() { + *x = PushAssetDepositKeysReq{} + if protoimpl.UnsafeEnabled { + mi := &file_asset_deposit_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushAssetDepositKeysReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushAssetDepositKeysReq) ProtoMessage() {} + +func (x *PushAssetDepositKeysReq) ProtoReflect() protoreflect.Message { + mi := &file_asset_deposit_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushAssetDepositKeysReq.ProtoReflect.Descriptor instead. +func (*PushAssetDepositKeysReq) Descriptor() ([]byte, []int) { + return file_asset_deposit_proto_rawDescGZIP(), []int{7} +} + +func (x *PushAssetDepositKeysReq) GetDepositKeys() map[string][]byte { + if x != nil { + return x.DepositKeys + } + return nil +} + +type PushAssetDepositKeysRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *PushAssetDepositKeysRes) Reset() { + *x = PushAssetDepositKeysRes{} + if protoimpl.UnsafeEnabled { + mi := &file_asset_deposit_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushAssetDepositKeysRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushAssetDepositKeysRes) ProtoMessage() {} + +func (x *PushAssetDepositKeysRes) ProtoReflect() protoreflect.Message { + mi := &file_asset_deposit_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushAssetDepositKeysRes.ProtoReflect.Descriptor instead. +func (*PushAssetDepositKeysRes) Descriptor() ([]byte, []int) { + return file_asset_deposit_proto_rawDescGZIP(), []int{8} +} + +var File_asset_deposit_proto protoreflect.FileDescriptor + +var file_asset_deposit_proto_rawDesc = []byte{ + 0x0a, 0x13, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x22, 0xd4, + 0x01, 0x0a, 0x18, 0x4e, 0x65, 0x77, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x12, 0x19, 0x0a, 0x08, 0x61, + 0x73, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, + 0x73, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x34, + 0x0a, 0x16, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x50, 0x75, + 0x62, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x73, 0x76, 0x5f, 0x65, 0x78, + 0x70, 0x69, 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x63, 0x73, 0x76, 0x45, + 0x78, 0x70, 0x69, 0x72, 0x79, 0x22, 0xc4, 0x01, 0x0a, 0x18, 0x4e, 0x65, 0x77, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x49, + 0x64, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x12, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, + 0x6b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x16, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x14, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x49, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x65, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x22, 0x41, 0x0a, 0x1e, + 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x12, 0x1f, + 0x0a, 0x0b, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x49, 0x64, 0x73, 0x22, + 0xbd, 0x01, 0x0a, 0x1e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x41, 0x73, 0x73, 0x65, + 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x12, 0x5b, 0x0a, 0x0c, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x5f, 0x6b, 0x65, + 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, + 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x41, 0x73, 0x73, 0x65, 0x74, + 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x2e, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x0b, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x1a, + 0x3e, 0x0a, 0x10, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, + 0x6e, 0x0a, 0x16, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x50, + 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, + 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x1f, + 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x22, + 0x7e, 0x0a, 0x1b, 0x50, 0x75, 0x73, 0x68, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, + 0x73, 0x69, 0x74, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x12, 0x42, + 0x0a, 0x0c, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x50, 0x61, 0x72, 0x74, 0x69, + 0x61, 0x6c, 0x53, 0x69, 0x67, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x69, + 0x67, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x68, 0x74, 0x6c, 0x63, 0x50, 0x73, 0x62, 0x74, 0x22, + 0x1d, 0x0a, 0x1b, 0x50, 0x75, 0x73, 0x68, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, + 0x73, 0x69, 0x74, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x22, 0xaf, + 0x01, 0x0a, 0x17, 0x50, 0x75, 0x73, 0x68, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, + 0x73, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x12, 0x54, 0x0a, 0x0c, 0x64, 0x65, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x31, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x52, + 0x65, 0x71, 0x2e, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x0b, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x73, + 0x1a, 0x3e, 0x0a, 0x10, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x22, 0x19, 0x0a, 0x17, 0x50, 0x75, 0x73, 0x68, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x2a, 0x33, 0x0a, 0x1b, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x53, + 0x53, 0x45, 0x54, 0x5f, 0x44, 0x45, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x5f, 0x56, 0x30, 0x10, 0x00, + 0x32, 0x9d, 0x03, 0x0a, 0x13, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x57, 0x0a, 0x0f, 0x4e, 0x65, 0x77, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x12, 0x21, 0x2e, 0x6c, 0x6f, + 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x21, + 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x73, 0x73, 0x65, + 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x12, 0x69, 0x0a, 0x15, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x6c, 0x6f, 0x6f, + 0x70, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x52, 0x65, 0x71, 0x1a, 0x27, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, + 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x12, 0x66, 0x0a, 0x18, + 0x50, 0x75, 0x73, 0x68, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, + 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x73, 0x12, 0x24, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, + 0x70, 0x63, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, + 0x73, 0x69, 0x74, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x24, + 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, + 0x73, 0x52, 0x65, 0x73, 0x12, 0x5a, 0x0a, 0x14, 0x50, 0x75, 0x73, 0x68, 0x41, 0x73, 0x73, 0x65, + 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x20, 0x2e, 0x6c, + 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x41, 0x73, 0x73, 0x65, 0x74, + 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x20, + 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, + 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, + 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x6f, 0x6f, + 0x70, 0x2f, 0x73, 0x77, 0x61, 0x70, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_asset_deposit_proto_rawDescOnce sync.Once + file_asset_deposit_proto_rawDescData = file_asset_deposit_proto_rawDesc +) + +func file_asset_deposit_proto_rawDescGZIP() []byte { + file_asset_deposit_proto_rawDescOnce.Do(func() { + file_asset_deposit_proto_rawDescData = protoimpl.X.CompressGZIP(file_asset_deposit_proto_rawDescData) + }) + return file_asset_deposit_proto_rawDescData +} + +var file_asset_deposit_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_asset_deposit_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_asset_deposit_proto_goTypes = []any{ + (AssetDepositProtocolVersion)(0), // 0: looprpc.AssetDepositProtocolVersion + (*NewAssetDepositServerReq)(nil), // 1: looprpc.NewAssetDepositServerReq + (*NewAssetDepositServerRes)(nil), // 2: looprpc.NewAssetDepositServerRes + (*WithdrawAssetDepositsServerReq)(nil), // 3: looprpc.WithdrawAssetDepositsServerReq + (*WithdrawAssetDepositsServerRes)(nil), // 4: looprpc.WithdrawAssetDepositsServerRes + (*AssetDepositPartialSig)(nil), // 5: looprpc.AssetDepositPartialSig + (*PushAssetDepositHtlcSigsReq)(nil), // 6: looprpc.PushAssetDepositHtlcSigsReq + (*PushAssetDepositHtlcSigsRes)(nil), // 7: looprpc.PushAssetDepositHtlcSigsRes + (*PushAssetDepositKeysReq)(nil), // 8: looprpc.PushAssetDepositKeysReq + (*PushAssetDepositKeysRes)(nil), // 9: looprpc.PushAssetDepositKeysRes + nil, // 10: looprpc.WithdrawAssetDepositsServerRes.DepositKeysEntry + nil, // 11: looprpc.PushAssetDepositKeysReq.DepositKeysEntry +} +var file_asset_deposit_proto_depIdxs = []int32{ + 10, // 0: looprpc.WithdrawAssetDepositsServerRes.deposit_keys:type_name -> looprpc.WithdrawAssetDepositsServerRes.DepositKeysEntry + 5, // 1: looprpc.PushAssetDepositHtlcSigsReq.partial_sigs:type_name -> looprpc.AssetDepositPartialSig + 11, // 2: looprpc.PushAssetDepositKeysReq.deposit_keys:type_name -> looprpc.PushAssetDepositKeysReq.DepositKeysEntry + 1, // 3: looprpc.AssetDepositService.NewAssetDeposit:input_type -> looprpc.NewAssetDepositServerReq + 3, // 4: looprpc.AssetDepositService.WithdrawAssetDeposits:input_type -> looprpc.WithdrawAssetDepositsServerReq + 6, // 5: looprpc.AssetDepositService.PushAssetDepositHtlcSigs:input_type -> looprpc.PushAssetDepositHtlcSigsReq + 8, // 6: looprpc.AssetDepositService.PushAssetDepositKeys:input_type -> looprpc.PushAssetDepositKeysReq + 2, // 7: looprpc.AssetDepositService.NewAssetDeposit:output_type -> looprpc.NewAssetDepositServerRes + 4, // 8: looprpc.AssetDepositService.WithdrawAssetDeposits:output_type -> looprpc.WithdrawAssetDepositsServerRes + 7, // 9: looprpc.AssetDepositService.PushAssetDepositHtlcSigs:output_type -> looprpc.PushAssetDepositHtlcSigsRes + 9, // 10: looprpc.AssetDepositService.PushAssetDepositKeys:output_type -> looprpc.PushAssetDepositKeysRes + 7, // [7:11] is the sub-list for method output_type + 3, // [3:7] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_asset_deposit_proto_init() } +func file_asset_deposit_proto_init() { + if File_asset_deposit_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_asset_deposit_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*NewAssetDepositServerReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_asset_deposit_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*NewAssetDepositServerRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_asset_deposit_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*WithdrawAssetDepositsServerReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_asset_deposit_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*WithdrawAssetDepositsServerRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_asset_deposit_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*AssetDepositPartialSig); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_asset_deposit_proto_msgTypes[5].Exporter = func(v any, i int) any { + switch v := v.(*PushAssetDepositHtlcSigsReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_asset_deposit_proto_msgTypes[6].Exporter = func(v any, i int) any { + switch v := v.(*PushAssetDepositHtlcSigsRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_asset_deposit_proto_msgTypes[7].Exporter = func(v any, i int) any { + switch v := v.(*PushAssetDepositKeysReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_asset_deposit_proto_msgTypes[8].Exporter = func(v any, i int) any { + switch v := v.(*PushAssetDepositKeysRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_asset_deposit_proto_rawDesc, + NumEnums: 1, + NumMessages: 11, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_asset_deposit_proto_goTypes, + DependencyIndexes: file_asset_deposit_proto_depIdxs, + EnumInfos: file_asset_deposit_proto_enumTypes, + MessageInfos: file_asset_deposit_proto_msgTypes, + }.Build() + File_asset_deposit_proto = out.File + file_asset_deposit_proto_rawDesc = nil + file_asset_deposit_proto_goTypes = nil + file_asset_deposit_proto_depIdxs = nil +} diff --git a/swapserverrpc/asset_deposit.proto b/swapserverrpc/asset_deposit.proto new file mode 100644 index 000000000..20c87fa4f --- /dev/null +++ b/swapserverrpc/asset_deposit.proto @@ -0,0 +1,121 @@ +syntax = "proto3"; + +// We can't change this to swapserverrpc, it would be a breaking change because +// the package name is also contained in the HTTP URIs and old clients would +// call the wrong endpoints. Luckily with the go_package option we can have +// different golang and RPC package names to fix protobuf namespace conflicts. +package looprpc; + +option go_package = "github.com/lightninglabs/loop/swapserverrpc"; + +// AssetDepositService is the service handling asset deposit creation and +// spending. Asset deposits are used in asset loop-in swaps. +service AssetDepositService { + // NewAssetDeposit creates a new asset deposit address. + rpc NewAssetDeposit (NewAssetDepositServerReq) + returns (NewAssetDepositServerRes); + + // WithdrawAssetDeposit withdraws asset deposits to the user's wallet. + rpc WithdrawAssetDeposits (WithdrawAssetDepositsServerReq) + returns (WithdrawAssetDepositsServerRes); + + // PushHtlcSigs pushes a MuSig2 partial signatures to the server spending + // one ore more deposits to a zero fee HTLC. + rpc PushAssetDepositHtlcSigs (PushAssetDepositHtlcSigsReq) + returns (PushAssetDepositHtlcSigsRes); + + // PushKeys pushes (ie reveals) the private keys of one ore more deposits to + // the server. + rpc PushAssetDepositKeys (PushAssetDepositKeysReq) + returns (PushAssetDepositKeysRes); +} + +// NewAssetDepositServerReq is the request to the Server to create a new asset +// deposit. +message NewAssetDepositServerReq { + // asset_id is the id of the asset to deposit. + bytes asset_id = 1; + + // amount is the amount of the asset to deposit. + uint64 amount = 2; + + // client_internal_key is the client's internal pubkey used for the asset + // deposit deposit MuSig2 key. + bytes client_internal_pubkey = 3; + + // client_script_key is the client's script pubkey used for the asset + // deposit timeout script. + bytes client_script_pubkey = 4; + + // csv_expiry is the CSV expiry for the deposit transaction. + int32 csv_expiry = 5; +} + +// NewAssetDepositServerRes is the Server's response to a NewAssetDeposit +// request. +message NewAssetDepositServerRes { + // deposit_id is the unique id of the deposit. + string deposit_id = 1; + + // server_script_pubkey is the script pubkey of the server used for the + // asset deposit spending HTLC script. + bytes server_script_pubkey = 2; + + // server_internal_pubkey is the public key of the server used for the asset + // deposit MuSig2 key. + bytes server_internal_pubkey = 3; + + // deposit_addr is the TAP address to deposit the asset to. + string deposit_addr = 4; +} + +message WithdrawAssetDepositsServerReq { + repeated string deposit_ids = 1; +} + +message WithdrawAssetDepositsServerRes { + map deposit_keys = 1; +} + +// AssetDepositPartialSig holds a nonce and partial signature spending a +// deposit. +message AssetDepositPartialSig { + // deposit_id is the deposit ID corresponding to this partial signature. + string deposit_id = 1; + + // nonce is the nonce used for generating this signature. + bytes nonce = 2; + + // partial_sig is the partial signature for spending the deposit. + bytes partial_sig = 3; +} + +// PushAssetDepositHtlcSigsReq holds partial signatures spending one or more +// deposits and the zero fee HTLC spending them. +message PushAssetDepositHtlcSigsReq { + // partial_sigs holds the partial signatures for the deposits spent by the + // HTLC. The inputs of the HTLC will be in the same order defined here. + repeated AssetDepositPartialSig partial_sigs = 1; + + // htlc_psbt is the HTLC psbt. + bytes htlc_psbt = 2; +} + +message PushAssetDepositHtlcSigsRes { +} + +// PushAssetDepositKeysReq holds private keys of one or more deposits. +message PushAssetDepositKeysReq { + // deposit_keys is a map wich maps deposit_id to deposit internal private + // key. + map deposit_keys = 1; +} + +message PushAssetDepositKeysRes { +} + +// AssetDepositProtocolVersion is the version of the asset deposit protocol. +enum AssetDepositProtocolVersion { + // V0 is the first version of the asset deposit protocol. + ASSET_DEPOSIT_V0 = 0; +}; diff --git a/swapserverrpc/asset_deposit_grpc.pb.go b/swapserverrpc/asset_deposit_grpc.pb.go new file mode 100644 index 000000000..03dcd3e57 --- /dev/null +++ b/swapserverrpc/asset_deposit_grpc.pb.go @@ -0,0 +1,221 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package swapserverrpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// AssetDepositServiceClient is the client API for AssetDepositService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AssetDepositServiceClient interface { + // NewAssetDeposit creates a new asset deposit address. + NewAssetDeposit(ctx context.Context, in *NewAssetDepositServerReq, opts ...grpc.CallOption) (*NewAssetDepositServerRes, error) + // WithdrawAssetDeposit withdraws asset deposits to the user's wallet. + WithdrawAssetDeposits(ctx context.Context, in *WithdrawAssetDepositsServerReq, opts ...grpc.CallOption) (*WithdrawAssetDepositsServerRes, error) + // PushHtlcSigs pushes a MuSig2 partial signatures to the server spending + // one ore more deposits to a zero fee HTLC. + PushAssetDepositHtlcSigs(ctx context.Context, in *PushAssetDepositHtlcSigsReq, opts ...grpc.CallOption) (*PushAssetDepositHtlcSigsRes, error) + // PushKeys pushes (ie reveals) the private keys of one ore more deposits to + // the server. + PushAssetDepositKeys(ctx context.Context, in *PushAssetDepositKeysReq, opts ...grpc.CallOption) (*PushAssetDepositKeysRes, error) +} + +type assetDepositServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewAssetDepositServiceClient(cc grpc.ClientConnInterface) AssetDepositServiceClient { + return &assetDepositServiceClient{cc} +} + +func (c *assetDepositServiceClient) NewAssetDeposit(ctx context.Context, in *NewAssetDepositServerReq, opts ...grpc.CallOption) (*NewAssetDepositServerRes, error) { + out := new(NewAssetDepositServerRes) + err := c.cc.Invoke(ctx, "/looprpc.AssetDepositService/NewAssetDeposit", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *assetDepositServiceClient) WithdrawAssetDeposits(ctx context.Context, in *WithdrawAssetDepositsServerReq, opts ...grpc.CallOption) (*WithdrawAssetDepositsServerRes, error) { + out := new(WithdrawAssetDepositsServerRes) + err := c.cc.Invoke(ctx, "/looprpc.AssetDepositService/WithdrawAssetDeposits", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *assetDepositServiceClient) PushAssetDepositHtlcSigs(ctx context.Context, in *PushAssetDepositHtlcSigsReq, opts ...grpc.CallOption) (*PushAssetDepositHtlcSigsRes, error) { + out := new(PushAssetDepositHtlcSigsRes) + err := c.cc.Invoke(ctx, "/looprpc.AssetDepositService/PushAssetDepositHtlcSigs", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *assetDepositServiceClient) PushAssetDepositKeys(ctx context.Context, in *PushAssetDepositKeysReq, opts ...grpc.CallOption) (*PushAssetDepositKeysRes, error) { + out := new(PushAssetDepositKeysRes) + err := c.cc.Invoke(ctx, "/looprpc.AssetDepositService/PushAssetDepositKeys", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AssetDepositServiceServer is the server API for AssetDepositService service. +// All implementations must embed UnimplementedAssetDepositServiceServer +// for forward compatibility +type AssetDepositServiceServer interface { + // NewAssetDeposit creates a new asset deposit address. + NewAssetDeposit(context.Context, *NewAssetDepositServerReq) (*NewAssetDepositServerRes, error) + // WithdrawAssetDeposit withdraws asset deposits to the user's wallet. + WithdrawAssetDeposits(context.Context, *WithdrawAssetDepositsServerReq) (*WithdrawAssetDepositsServerRes, error) + // PushHtlcSigs pushes a MuSig2 partial signatures to the server spending + // one ore more deposits to a zero fee HTLC. + PushAssetDepositHtlcSigs(context.Context, *PushAssetDepositHtlcSigsReq) (*PushAssetDepositHtlcSigsRes, error) + // PushKeys pushes (ie reveals) the private keys of one ore more deposits to + // the server. + PushAssetDepositKeys(context.Context, *PushAssetDepositKeysReq) (*PushAssetDepositKeysRes, error) + mustEmbedUnimplementedAssetDepositServiceServer() +} + +// UnimplementedAssetDepositServiceServer must be embedded to have forward compatible implementations. +type UnimplementedAssetDepositServiceServer struct { +} + +func (UnimplementedAssetDepositServiceServer) NewAssetDeposit(context.Context, *NewAssetDepositServerReq) (*NewAssetDepositServerRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method NewAssetDeposit not implemented") +} +func (UnimplementedAssetDepositServiceServer) WithdrawAssetDeposits(context.Context, *WithdrawAssetDepositsServerReq) (*WithdrawAssetDepositsServerRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method WithdrawAssetDeposits not implemented") +} +func (UnimplementedAssetDepositServiceServer) PushAssetDepositHtlcSigs(context.Context, *PushAssetDepositHtlcSigsReq) (*PushAssetDepositHtlcSigsRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method PushAssetDepositHtlcSigs not implemented") +} +func (UnimplementedAssetDepositServiceServer) PushAssetDepositKeys(context.Context, *PushAssetDepositKeysReq) (*PushAssetDepositKeysRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method PushAssetDepositKeys not implemented") +} +func (UnimplementedAssetDepositServiceServer) mustEmbedUnimplementedAssetDepositServiceServer() {} + +// UnsafeAssetDepositServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AssetDepositServiceServer will +// result in compilation errors. +type UnsafeAssetDepositServiceServer interface { + mustEmbedUnimplementedAssetDepositServiceServer() +} + +func RegisterAssetDepositServiceServer(s grpc.ServiceRegistrar, srv AssetDepositServiceServer) { + s.RegisterService(&AssetDepositService_ServiceDesc, srv) +} + +func _AssetDepositService_NewAssetDeposit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(NewAssetDepositServerReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetDepositServiceServer).NewAssetDeposit(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetDepositService/NewAssetDeposit", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetDepositServiceServer).NewAssetDeposit(ctx, req.(*NewAssetDepositServerReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _AssetDepositService_WithdrawAssetDeposits_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WithdrawAssetDepositsServerReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetDepositServiceServer).WithdrawAssetDeposits(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetDepositService/WithdrawAssetDeposits", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetDepositServiceServer).WithdrawAssetDeposits(ctx, req.(*WithdrawAssetDepositsServerReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _AssetDepositService_PushAssetDepositHtlcSigs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PushAssetDepositHtlcSigsReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetDepositServiceServer).PushAssetDepositHtlcSigs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetDepositService/PushAssetDepositHtlcSigs", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetDepositServiceServer).PushAssetDepositHtlcSigs(ctx, req.(*PushAssetDepositHtlcSigsReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _AssetDepositService_PushAssetDepositKeys_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PushAssetDepositKeysReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetDepositServiceServer).PushAssetDepositKeys(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetDepositService/PushAssetDepositKeys", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetDepositServiceServer).PushAssetDepositKeys(ctx, req.(*PushAssetDepositKeysReq)) + } + return interceptor(ctx, in, info, handler) +} + +// AssetDepositService_ServiceDesc is the grpc.ServiceDesc for AssetDepositService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var AssetDepositService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "looprpc.AssetDepositService", + HandlerType: (*AssetDepositServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "NewAssetDeposit", + Handler: _AssetDepositService_NewAssetDeposit_Handler, + }, + { + MethodName: "WithdrawAssetDeposits", + Handler: _AssetDepositService_WithdrawAssetDeposits_Handler, + }, + { + MethodName: "PushAssetDepositHtlcSigs", + Handler: _AssetDepositService_PushAssetDepositHtlcSigs_Handler, + }, + { + MethodName: "PushAssetDepositKeys", + Handler: _AssetDepositService_PushAssetDepositKeys_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "asset_deposit.proto", +}