diff --git a/assets/client.go b/assets/client.go index 88c23efe5..e69136ced 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,436 @@ 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, label string) (*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, + Label: label, + } + + 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 +768,141 @@ 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) + + isComplete := func(event *taprpc.SendEvent) bool { + return event.SendState == + tapfreighter.SendStateComplete.String() } - return conn, nil + go func() { + for { + event, err := sendEventsClient.Recv() + if err != nil { + errChan <- err + + return + } + + if isComplete(event) { + resChan <- TapSendEvent{ + Transfer: event.Transfer, + } + return + } + } + }() + + return resChan, errChan, nil } diff --git a/assets/deposit/deposit.go b/assets/deposit/deposit.go new file mode 100644 index 000000000..12cd0229d --- /dev/null +++ b/assets/deposit/deposit.go @@ -0,0 +1,248 @@ +package deposit + +import ( + "context" + "encoding/hex" + "fmt" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/wtxmgr" + "github.com/lightninglabs/loop/assets" + "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 +} + +// Info holds publicly available information about an asset deposit. +// It is used to communicate deposit details to clients of the deposit Manager. +type Info 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 + + // Version 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 + + // SweepScriptKey is the script key of the swept asset. + SweepScriptKey *btcec.PublicKey + + // SweepInternalKey is the internal key of output of the swept asset. + SweepInternalKey *btcec.PublicKey +} + +// Copy creates a copy of the Info struct. +func (d *Info) Copy() *Info { + info := &Info{ + ID: d.ID, + Version: d.Version, + CreatedAt: d.CreatedAt, + Amount: d.Amount, + Addr: d.Addr, + State: d.State, + ConfirmationHeight: d.ConfirmationHeight, + Expiry: d.Expiry, + } + + if d.Outpoint != nil { + info.Outpoint = &wire.OutPoint{ + Hash: d.Outpoint.Hash, + Index: d.Outpoint.Index, + } + } + + if d.SweepScriptKey != nil { + pubKey := *d.SweepScriptKey + info.SweepScriptKey = &pubKey + } + + if d.SweepInternalKey != nil { + pubKey := *d.SweepInternalKey + info.SweepInternalKey = &pubKey + } + + return info +} + +// Deposit is the struct that holds all the information about an asset deposit. +type Deposit struct { + *Kit + + *Info + + // 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 +} + +// fundingLabel 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) fundingLabel() string { + return fmt.Sprintf("deposit funding %v", d.ID) +} + +// timeoutSweepLabel is a string label that we can use for marking a timeout +// sweep transfer. This is useful if we need to filter deposits. +func (d *Deposit) timeoutSweepLabel() string { + return fmt.Sprintf("deposit timeout sweep %v", d.ID) +} + +// withdrawLabel is a string label that we can use for marking a withdrawal +// sweep transfer. This is useful if we need to filter deposits. +func (d *Deposit) withdrawLabel() string { + return fmt.Sprintf("deposit withdraw sweep %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 +} + +// GenerateSweepKeys generates the sweep script key and internal key for the +// deposit sweep. +func (d *Deposit) GenerateSweepKeys(ctx context.Context, + client *assets.TapdClient) error { + + if d.SweepScriptKey != nil { + return nil + } + + scriptKey, internalKeyDesc, err := client.DeriveNewKeys(ctx) + if err != nil { + return err + } + + d.SweepScriptKey = scriptKey.PubKey + d.SweepInternalKey = internalKeyDesc.PubKey + + return nil +} diff --git a/assets/deposit/kit.go b/assets/deposit/kit.go new file mode 100644 index 000000000..351d50feb --- /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/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: amount, + SwapHash: swapHash, + CsvExpiry: csvExpiry, + AddressParams: d.chainParams, + } + + 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..eab68baca --- /dev/null +++ b/assets/deposit/manager.go @@ -0,0 +1,1374 @@ +package deposit + +import ( + "bytes" + "context" + "errors" + "fmt" + "strings" + "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/decred/dcrd/dcrec/secp256k1/v4" + "github.com/lightninglabs/lndclient" + "github.com/lightninglabs/loop/assets" + "github.com/lightninglabs/loop/assets/htlc" + "github.com/lightninglabs/loop/swapserverrpc" + "github.com/lightninglabs/taproot-assets/address" + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/rpcutils" + "github.com/lightninglabs/taproot-assets/taprpc" + "github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc" + "github.com/lightningnetwork/lnd/lntypes" + "google.golang.org/protobuf/proto" +) + +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(*Info) + +// 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 store. + store Store + + // 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 outer map key is the deposit ID, the inner map key is a unique + // subscriber ID. + subscribers map[string]map[uint64]DepositUpdateCallback + + // nextSubscriberID is the next available ID for a subscriber. + nextSubscriberID uint64 + + // 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 Store, + 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]map[uint64]DepositUpdateCallback), //nolint:lll + 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 + } + + // Register the deposit as known so that we can claim + // the funds committed to the OP_TRUE script. If the + // deposit is already registered, this will be a no-op. + err = m.registerDepositAsKnown(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) (func(), error) { + + done, err := m.scheduleNextCall() + if err != nil { + return func() {}, err + } + defer done() + + d, ok := m.deposits[depositID] + if !ok { + return func() {}, fmt.Errorf("deposit %s not found", depositID) + } + + log.Infof("Registering deposit state update subscriber for deposit: %s", + d.ID) + + // Get the next available subscriber ID. + subscriberID := m.nextSubscriberID + m.nextSubscriberID++ + + // If this is the first subscriber for this deposit, we need to create + // the inner map. + if _, ok := m.subscribers[d.ID]; !ok { + m.subscribers[d.ID] = make(map[uint64]DepositUpdateCallback) + } + + // Store the subscriber. + m.subscribers[d.ID][subscriberID] = subscriber + + // Send the current deposit info to the subscriber right away in a + // non-blocking way. + go subscriber(d.Info.Copy()) + + // Return a function that can be used to unsubscribe. + unsubscribe := func() { + done, err := m.scheduleNextCall() + if err != nil { + log.Errorf("Error unsubscribing from deposit "+ + "updates: %v", err) + return + } + defer done() + + log.Infof("Unsubscribing from deposit state updates for "+ + "deposit: %s, subscriber: %d", d.ID, subscriberID) + + // If the subscriber map for the deposit exists, delete the + // subscriber from it. + if depositSubscribers, ok := m.subscribers[d.ID]; ok { + delete(depositSubscribers, subscriberID) + + // If there are no more subscribers for this deposit, + // delete the deposit's entry from the subscribers map. + if len(depositSubscribers) == 0 { + delete(m.subscribers, d.ID) + } + } + } + + return unsubscribe, 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.Info.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) (Info, error) { + + clientKeyDesc, err := m.walletKit.DeriveNextKey( + ctx, AssetDepositKeyFamily, + ) + if err != nil { + return Info{}, err + } + clientInternalPubKey, _, err := DeriveSharedDepositKey( + ctx, m.signer, clientKeyDesc.PubKey, + ) + if err != nil { + return Info{}, 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 Info{}, err + } + + serverScriptPubKey, err := btcec.ParsePubKey(resp.ServerScriptPubkey) + if err != nil { + return Info{}, err + } + + serverInternalPubKey, err := btcec.ParsePubKey( + resp.ServerInternalPubkey, + ) + if err != nil { + return Info{}, err + } + + kit, err := NewKit( + clientKeyDesc.PubKey, clientInternalPubKey, serverScriptPubKey, + serverInternalPubKey, clientKeyDesc.KeyLocator, assetID, + csvExpiry, &m.addressParams, + ) + if err != nil { + return Info{}, err + } + + deposit := &Deposit{ + Kit: kit, + Info: &Info{ + 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 Info{}, err + } + + err = m.handleNewDeposit(ctx, deposit) + if err != nil { + log.Errorf("Unable to add deposit to active deposits: %v", err) + + return Info{}, err + } + + return *deposit.Info.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.fundingLabel(), + }, + ) + 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 +} + +// 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. Note that if the proof is + // already imoported then this will be a no-op and just return the + // last proof (ie the deposit proof). + 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 +} + +// registerDepositAsKnown registers the deposit with the tapd server so it +// can claim the funds committed to the OP_TRUE script. +func (m *Manager) registerDepositAsKnown(ctx context.Context, + d *Deposit) error { + + opTrueScriptKey, _, _, _, err := htlc.CreateOpTrueLeaf() + if err != nil { + return err + } + + // Declare the script key as known on the underlying tapd. Note that + // under the hood this is an upsert operation, so if the script is + // already known it will be a no-op. This is useful since we use the + // same OP_TRUE script key for all deposits. + _, err = m.tapClient.DeclareScriptKey( + ctx, &assetwalletrpc.DeclareScriptKeyRequest{ + ScriptKey: rpcutils.MarshalScriptKey( + opTrueScriptKey, + ), + }, + ) + if err != nil { + return err + } + + // Now let the underlying tapd know about the deposit transfer so it can + // then claim the funds committed to the OP_TRUE script. + opTrueScriptKeyBytes := opTrueScriptKey.PubKey.SerializeCompressed() + opTrueScriptKeyBytes[0] = secp256k1.PubKeyFormatCompressedEven + + _, err = m.tapClient.RegisterTransfer( + ctx, &taprpc.RegisterTransferRequest{ + AssetId: d.AssetID[:], + ScriptKey: opTrueScriptKeyBytes, + Outpoint: &taprpc.OutPoint{ + Txid: d.Outpoint.Hash[:], + OutputIndex: d.Outpoint.Index, + }, + }, + ) + if err != nil { + if strings.Contains(err.Error(), "already exists") { + log.Debugf("Deposit %v already registered as known: %v", + d.ID, err) + + return nil + } + + return err + } + + return nil +} + +// waitForDepositConfirmation waits for the deposit to be confirmed. +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.fundingLabel(), + ) + 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 +} + +// 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 { + log.Errorf("Unable to cache proof info for deposit %v: %v", + d.ID, err) + + return err + } + + err = m.registerDepositAsKnown(ctx, d) + if err != nil { + log.Errorf("Unable to register deposit %v as known: %v", + d.ID, err) + + 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 { + d.State = StateExpired + err := d.GenerateSweepKeys(ctx, m.tapClient) + if err != nil { + log.Errorf("Unable to generate sweep keys for deposit %v: %v", + d.ID, err) + } + + 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 { + 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, + ) + if err != nil { + return err + } + + lockID, err := d.lockID() + if err != nil { + return err + } + + snedResp, err := m.sweeper.PublishDepositTimeoutSweep( + ctx, d.Kit, d.Proof, asset.NewScriptKey(d.SweepScriptKey), + d.SweepInternalKey, d.timeoutSweepLabel(), + feeRateSatPerKw.FeePerVByte(), lockID, lockExpiration, + ) + if err != nil { + // TODO(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 + } + } + } + + // Start monitoring the sweep unless we're already doing so. + if _, ok := m.pendingSweeps[d.ID]; !ok { + err := m.waitForDepositSweep(ctx, d, d.timeoutSweepLabel()) + if err != nil { + log.Errorf("Unable to wait for deposit %v spend: %v", + d.ID, err) + + return err + } + + m.pendingSweeps[d.ID] = struct{}{} + } + + 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) waitForDepositSweep(ctx context.Context, d *Deposit, + label string) error { + + log.Infof("Waiting for deposit sweep confirmation %s", d.ID) + + eventChan, errChan, err := m.tapClient.WaitForSendComplete( + ctx, d.SweepScriptKey.SerializeCompressed(), label, + ) + if err != nil { + log.Errorf("unable to subscribe to send events for deposit "+ + "sweep: %v", err, + ) + } + + go func() { + select { + case event := <-eventChan: + // At this point we can consider the deposit confirmed. + err = m.handleDepositSpend(ctx, d, event.Transfer) + if err != nil { + m.criticalError(err) + } + + case err := <-errChan: + m.criticalError(err) + } + }() + + return nil +} + +func formatProtoJSON(resp proto.Message) (string, error) { + jsonBytes, err := taprpc.ProtoJSONMarshalOpts.Marshal(resp) + if err != nil { + return "", err + } + + return string(jsonBytes), nil +} + +func toJSON(resp proto.Message) string { + jsonStr, _ := formatProtoJSON(resp) + + return jsonStr +} + +// 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, + transfer *taprpc.AssetTransfer) 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: + 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 + } + + log.Tracef("Deposit %s spent in transfer: %s\n", d.ID, toJSON(transfer)) + + // TODO(bhandras): should save the spend details to the store? + err = m.handleDepositStateUpdate(ctx, d) + if err != nil { + 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 + } + + d.State = StateWithdrawn + err = d.GenerateSweepKeys(ctx, m.tapClient) + if err != nil { + log.Errorf("Unable to generate sweep keys for deposit "+ + "withdrawal %v: %v", d.ID, err) + + return err + } + + 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 + } + + serverKey, err := m.store.GetAssetDepositServerKey( + ctx, d.ID, + ) + if err != nil { + return err + } + + lockID, err := d.lockID() + if err != nil { + return err + } + + // TODO(bhandras): conf target should be dynamic/configrable. + const confTarget = 2 + feeRateSatPerKw, err := m.walletKit.EstimateFeeRate( + ctx, confTarget, + ) + if err != nil { + return err + } + + funder := true + sendResp, err := m.sweeper.PublishDepositSweepMuSig2( + ctx, d.Kit, funder, d.Proof, serverKey, + asset.NewScriptKey(d.SweepScriptKey), + d.SweepInternalKey, d.withdrawLabel(), + 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, + sendResp.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 + } + } + + // Start monitoring the sweep unless we're already doing so. + if _, ok := m.pendingSweeps[d.ID]; !ok { + err := m.waitForDepositSweep(ctx, d, d.withdrawLabel()) + if err != nil { + log.Errorf("Unable to wait for deposit %v "+ + "spend: %v", d.ID, err) + + return err + } + + m.pendingSweeps[d.ID] = struct{}{} + } + } + + 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 +} + +// PushHtlcSig will partially a deposit spending zero-fee HTLC and send the +// resulting signature to the swap server. +func (m *Manager) PushHtlcSig(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 + } + + funder := true + depositSigner := NewMuSig2Signer( + m.signer, deposit.Kit, funder, deposit.AnchorRootHash, + ) + + err = depositSigner.NewSession(ctx) + if err != nil { + return fmt.Errorf("Unable to create MuSig2 session: %w", err) + } + + publicNonce, err := depositSigner.PubNonce() + if err != nil { + return err + } + + partialSig, err := depositSigner.PartialSignMuSig2( + serverNonce, sigHash, + ) + if err != nil { + log.Errorf("Unable to create partial deposit signature %v: %v", + deposit.ID, err) + + return err + } + + var pktBuf bytes.Buffer + err = htlcPkt.Serialize(&pktBuf) + if err != nil { + return err + } + + // TODO(bhandras): the server should return the final signature. + _, err = m.depositServiceClient.PushAssetDepositHtlcSigs( + ctx, &swapserverrpc.PushAssetDepositHtlcSigsReq{ + Hash: hash[:], + CsvExpiry: csvExpiry, + PartialSigs: []*swapserverrpc.AssetDepositPartialSig{ + { + DepositId: depositID, + Nonce: publicNonce[:], + PartialSig: partialSig, + }, + }, + HtlcPsbt: pktBuf.Bytes(), + }, + ) + + return err +} diff --git a/assets/deposit/manager_test.go b/assets/deposit/manager_test.go new file mode 100644 index 000000000..7ae86c211 --- /dev/null +++ b/assets/deposit/manager_test.go @@ -0,0 +1,205 @@ +package deposit + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/loop/test" + "github.com/stretchr/testify/require" +) + +// mockStore is a mock implementation of the Store interface. +type mockStore struct { + updated bool +} + +// AddAssetDeposit is a mock implementation of the AddAssetDeposit method. +func (s *mockStore) AddAssetDeposit(context.Context, *Deposit) error { + return nil +} + +// AddDepositUpdate marks the store as updated. +func (s *mockStore) AddDepositUpdate(context.Context, *Info) error { + s.updated = true + return nil +} + +// UpdateDeposit is a mock implementation of the UpdateDeposit method. +func (s *mockStore) UpdateDeposit(context.Context, *Deposit) error { + s.updated = true + return nil +} + +// GetAllDeposits is a mock implementation of the GetAllDeposits method. +func (s *mockStore) GetAllDeposits(context.Context) ([]Deposit, error) { + return []Deposit{}, nil +} + +// GetActiveDeposits is a mock implementation of the GetActiveDeposits method. +func (s *mockStore) GetActiveDeposits(context.Context) ([]Deposit, error) { + return []Deposit{}, nil +} + +// SetAssetDepositServerKey is a mock implementation of the +// SetAssetDepositServerKey method. +func (s *mockStore) SetAssetDepositServerKey(context.Context, string, + *btcec.PrivateKey) error { + + return nil +} + +// GetAssetDepositServerKey is a mock implementation of the +// GetAssetDepositServerKey method. +func (s *mockStore) GetAssetDepositServerKey(context.Context, string) ( + *btcec.PrivateKey, error) { + + // Return a dummy private key for testing + privKey, _ := btcec.NewPrivateKey() + return privKey, nil +} + +// testAddDeposit is a helper function that (intrusively) adds a deposit to the +// manager. +func testAddDeposit(t *testing.T, m *Manager, d *Deposit) { + t.Helper() + + done, err := m.scheduleNextCall() + require.NoError(t, err) + defer done() + + m.deposits[d.Info.ID] = d +} + +// testUpdateDeposit updates a deposit's state in a race-safe way by using the +// manager's internal synchronization. +func testUpdateDeposit(t *testing.T, m *Manager, d *Deposit, state State, + outpoint *wire.OutPoint) { + + t.Helper() + + done, err := m.scheduleNextCall() + require.NoError(t, err) + defer done() + + d.State = state + if outpoint != nil { + d.Outpoint = outpoint + } + + err = m.handleDepositStateUpdate(m.runCtx(), d) + require.NoError(t, err) +} + +// TestSubscribeDepositUpdates tests the subscription and unsubscription of +// deposit updates. +func TestSubscribeDepositUpdates(t *testing.T) { + // The test guard ensures that there are no goroutines left running + // after the test completes. + defer test.Guard(t)() + + const updateTimeout = 500 * time.Millisecond + + // Create a mock lnd instance to be able to use the chain notifier. + lnd := test.NewMockLnd() + store := &mockStore{} + + // Create a new manager with a mock clock and chain notifier. + m := NewManager( + nil, nil, nil, lnd.ChainNotifier, nil, store, + &chaincfg.SimNetParams, + ) + + // We'll use this context for our calls. + ctxb := context.Background() + + // Start the manager run loop. + ctxc, cancel := context.WithCancel(ctxb) + var wg sync.WaitGroup + + wg.Add(1) + go func() { + defer wg.Done() + + err := m.Run(ctxc, 0) + require.NoError(t, err) + }() + + // Create a dummy deposit and add it to the manager. + depositID := "test_deposit" + testDeposit := &Deposit{ + Info: &Info{ + ID: depositID, + State: StatePending, + }, + } + testAddDeposit(t, m, testDeposit) + + // Create a channel to receive deposit updates. + updateChan := make(chan *Info) + + // Define the update callback. + updateCallback := func(info *Info) { + updateChan <- info + } + + // assertUpdate is a helper function that waits for a deposit update and + // asserts its state. + assertUpdate := func(expectedState State) { + t.Helper() + + select { + case update := <-updateChan: + require.Equal(t, depositID, update.ID) + require.Equal(t, expectedState, update.State) + + case <-time.After(updateTimeout): + t.Fatalf("expected to receive deposit update with "+ + "state %v", expectedState) + } + } + + // Subscribe to deposit updates. + unsubscribe, err := m.SubscribeDepositUpdates( + depositID, updateCallback, + ) + require.NoError(t, err) + require.NotNil(t, unsubscribe) + + // The subscriber should receive the current deposit info right away. + assertUpdate(StatePending) + + // Trigger a deposit state update using the helper function. + testUpdateDeposit(t, m, testDeposit, StateConfirmed, &wire.OutPoint{ + Hash: chainhash.Hash{1, 2, 3}, + Index: 0, + }) + + // The subscriber should receive the update. + assertUpdate(StateConfirmed) + + // Now, unsubscribe. + unsubscribe() + + // Trigger another deposit state update. + testUpdateDeposit(t, m, testDeposit, StateSwept, nil) + + // The subscriber should not receive any more updates. + select { + case <-updateChan: + t.Fatal("expected not to receive deposit update after " + + "unsubscribing") + + case <-time.After(updateTimeout): + // All good, no update received. + } + + // Stop the manager and wait until it finishes. + cancel() + wg.Wait() +} diff --git a/assets/deposit/protocol.go b/assets/deposit/protocol.go new file mode 100644 index 000000000..61b32416e --- /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 <= 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..7879a4646 --- /dev/null +++ b/assets/deposit/server.go @@ -0,0 +1,216 @@ +package deposit + +import ( + "context" + "encoding/hex" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "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 length: 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, + } + + if d.Outpoint != nil { + rpcDeposit.AnchorOutpoint = d.Outpoint.String() + } + + if d.SweepScriptKey != nil { + rpcDeposit.SweepScriptKey = hex.EncodeToString( + d.SweepScriptKey.SerializeCompressed(), + ) + } + + if d.SweepInternalKey != nil { + rpcDeposit.SweepInternalKey = hex.EncodeToString( + d.SweepInternalKey.SerializeCompressed(), + ) + } + + 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 +} + +// PushAssetDepositHtlcSig is the rpc endpoint for loop clients to push partial +// signatures for asset deposit spending zero fee HTLCs to the server. +func (s *Server) PushAssetDepositHtlcSig(ctx context.Context, + in *looprpc.PushAssetDepositHtlcSigRequest) ( + *looprpc.PushAssetDepositHtlcSigResponse, error) { + + if len(in.Nonce) != musig2.PubNonceSize { + return nil, status.Error(codes.InvalidArgument, + fmt.Sprintf("invalid nonce length: expected %d bytes, "+ + "got %d", musig2.PubNonceSize, len(in.Nonce))) + } + + if len(in.PreimageHash) != lntypes.HashSize { + return nil, status.Error(codes.InvalidArgument, + fmt.Sprintf("invalid preimage hash length: expected "+ + "%d bytes, got %d", lntypes.HashSize, + len(in.PreimageHash))) + } + + var ( + nonce [musig2.PubNonceSize]byte + preimageHash lntypes.Hash + ) + copy(nonce[:], in.Nonce) + copy(preimageHash[:], in.PreimageHash) + + err := s.manager.PushHtlcSig( + ctx, in.DepositId, nonce, preimageHash, in.CsvExpiry, + ) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &looprpc.PushAssetDepositHtlcSigResponse{}, nil +} diff --git a/assets/deposit/signer.go b/assets/deposit/signer.go new file mode 100644 index 000000000..b670da5ae --- /dev/null +++ b/assets/deposit/signer.go @@ -0,0 +1,112 @@ +package deposit + +import ( + "bytes" + "context" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/lightninglabs/lndclient" + "github.com/lightningnetwork/lnd/input" +) + +type MuSig2Signer struct { + signer lndclient.SignerClient + + scriptKey *btcec.PublicKey + otherPubKey *btcec.PublicKey + anchorRootHash []byte + + session input.MuSig2Session +} + +func NewMuSig2Signer(signer lndclient.SignerClient, deposit *Kit, funder bool, + anchorRootHash []byte) *MuSig2Signer { + + scriptKey := deposit.FunderScriptKey + otherPubKey := deposit.CoSignerInternalKey + if !funder { + scriptKey = deposit.CoSignerScriptKey + otherPubKey = deposit.FunderInternalKey + } + + return &MuSig2Signer{ + signer: signer, + scriptKey: scriptKey, + otherPubKey: otherPubKey, + anchorRootHash: anchorRootHash, + } +} + +func (s *MuSig2Signer) NewSession(ctx context.Context) error { + // Derive the internal key that will be used to sign the message. + signingPubKey, signingPrivKey, err := DeriveSharedDepositKey( + ctx, s.signer, s.scriptKey, + ) + if err != nil { + return err + } + + pubKeys := []*btcec.PublicKey{ + signingPubKey, s.otherPubKey, + } + + tweaks := &input.MuSig2Tweaks{ + TaprootTweak: s.anchorRootHash, + } + + _, session, err := input.MuSig2CreateContext( + input.MuSig2Version100RC2, signingPrivKey, pubKeys, tweaks, nil, + ) + if err != nil { + return fmt.Errorf("error creating signing context: %w", err) + } + + s.session = session + + return nil +} + +func (s *MuSig2Signer) Session() input.MuSig2Session { + return s.session +} + +func (s *MuSig2Signer) PubNonce() ([musig2.PubNonceSize]byte, error) { + // If we don't have a session, we can't return a public nonce. + if s.session == nil { + return [musig2.PubNonceSize]byte{}, fmt.Errorf("no session " + + "available") + } + + // Return the public nonce of the current session. + return s.session.PublicNonce(), nil +} + +// PartialSignMuSig2 is used to partially sign a message hash with the deposit's +// keys. +func (s *MuSig2Signer) PartialSignMuSig2(otherNonce [musig2.PubNonceSize]byte, + message [32]byte) ([]byte, error) { + + if s.session == nil { + return nil, fmt.Errorf("no session available") + } + + _, err := s.session.RegisterPubNonce(otherNonce) + if err != nil { + return nil, err + } + + partialSig, err := input.MuSig2Sign(s.session, message, true) + if err != nil { + return nil, err + } + + var buf bytes.Buffer + err = partialSig.Encode(&buf) + if err != nil { + return nil, fmt.Errorf("error encoding partial sig: %w", err) + } + + return buf.Bytes(), nil +} diff --git a/assets/deposit/sql_store.go b/assets/deposit/sql_store.go new file mode 100644 index 000000000..7f1f81218 --- /dev/null +++ b/assets/deposit/sql_store.go @@ -0,0 +1,350 @@ +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) + + SetAssetDepositSweepKeys(ctx context.Context, + arg sqlc.SetAssetDepositSweepKeysParams) 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: + scriptKey := d.SweepScriptKey.SerializeCompressed() + internalKey := d.SweepInternalKey.SerializeCompressed() + err := tx.SetAssetDepositSweepKeys( + ctx, sqlc.SetAssetDepositSweepKeysParams{ + DepositID: d.ID, + SweepScriptPubkey: scriptKey, + SweepInternalPubkey: internalKey, + }, + ) + if err != nil { + return err + } + } + + return tx.UpdateDepositState( + ctx, sqlc.UpdateDepositStateParams{ + DepositID: d.ID, + UpdateState: int32(d.State), + UpdateTimestamp: s.clock.Now().UTC(), + }, + ) + }) +} + +// GetAllDeposits returns all deposits known to the store. +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 := &Info{ + ID: sqlDeposit.DepositID, + Version: AssetDepositProtocolVersion( + sqlDeposit.ProtocolVersion, + ), + CreatedAt: sqlDeposit.CreatedAt.Local(), //nolint:gosmopolitan + 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 len(sqlDeposit.SweepInternalPubkey) > 0 { + sweepInternalPubKey, err := btcec.ParsePubKey( + sqlDeposit.SweepInternalPubkey, + ) + if err != nil { + return Deposit{}, err + } + depositInfo.SweepInternalKey = sweepInternalPubKey + } + + if len(sqlDeposit.SweepScriptPubkey) > 0 { + sweepScriptPubKey, err := btcec.ParsePubKey( + sqlDeposit.SweepScriptPubkey, + ) + if err != nil { + return Deposit{}, err + } + depositInfo.SweepScriptKey = sweepScriptPubKey + } + + 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, + Info: 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/store.go b/assets/deposit/store.go new file mode 100644 index 000000000..c530f6e22 --- /dev/null +++ b/assets/deposit/store.go @@ -0,0 +1,34 @@ +package deposit + +import ( + "context" + + "github.com/btcsuite/btcd/btcec/v2" +) + +// Store defines the interface that the Manager requires from the storage layer. +type Store interface { + // AddAssetDeposit adds a new asset deposit to the database. + AddAssetDeposit(ctx context.Context, d *Deposit) error + + // UpdateDeposit updates the deposit state and extends the deposit + // update log in the SQL store. + UpdateDeposit(ctx context.Context, d *Deposit) error + + // GetAllDeposits returns all deposits known to the store. + GetAllDeposits(ctx context.Context) ([]Deposit, error) + + // GetActiveDeposits returns all active deposits from the database. + // Active deposits are those that have not yet been spent or swept. + GetActiveDeposits(ctx context.Context) ([]Deposit, error) + + // SetAssetDepositServerKey sets the server's internal key for the given + // asset deposit. + SetAssetDepositServerKey(ctx context.Context, depositID string, + key *btcec.PrivateKey) error + + // GetAssetDepositServerKey gets the server's internal key for the given + // asset deposit. + GetAssetDepositServerKey(ctx context.Context, depositID string) ( + *btcec.PrivateKey, error) +} diff --git a/assets/deposit/sweeper.go b/assets/deposit/sweeper.go new file mode 100644 index 000000000..b39991f91 --- /dev/null +++ b/assets/deposit/sweeper.go @@ -0,0 +1,417 @@ +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/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/asset" + "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/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 an interactive deposit sweep using the +// MuSig2 keyspend path. +func (s *Sweeper) PublishDepositSweepMuSig2(ctx context.Context, deposit *Kit, + funder bool, depositProof *proof.Proof, + otherInternalKey *btcec.PrivateKey, sweepScriptKey asset.ScriptKey, + sweepInternalKey *btcec.PublicKey, label string, + 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 + } + + // Create the sweep vpacket which is simply sweeping the asset on the + // OP_TRUE output to a new output with the provided script and internal + // keys. + sweepVpkt, err := assets.CreateOpTrueSweepVpkt( + ctx, []*proof.Proof{depositProof}, sweepScriptKey, + sweepInternalKey, nil, &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, + ) + if err != nil { + return nil, err + } + + 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, label, + ) + + 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, sweepScriptKey asset.ScriptKey, + sweepInternalKey *btcec.PublicKey, label string, + 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 a new output with the provided script and internal + // keys. + sweepVpkt, err := assets.CreateOpTrueSweepVpkt( + ctx, []*proof.Proof{depositProof}, sweepScriptKey, + sweepInternalKey, nil, &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, label, + ) + 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 + } + htlcScriptKey := asset.NewScriptKey(&htlcAddr.ScriptKey) + + // 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}, htlcScriptKey, + &htlcAddr.InternalKey, htlcAddr.TapscriptSibling, + &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 +} diff --git a/assets/htlc/script.go b/assets/htlc/script.go new file mode 100644 index 000000000..80f6707b9 --- /dev/null +++ b/assets/htlc/script.go @@ -0,0 +1,106 @@ +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, csvOne 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[:])) + // OP_EQUAL will leave 0 or 1 on the stack depending on whether the hash + // matches. + // - If it matches and CSV is not used, the script will + // evaulate to true. + // - If it matches and CSV is used, we'll have 1 on the stack which is + // used to verify the CSV condition. + // - If it does not match, we'll have 0 on the stack which will cause + // the script to fail even if CSV is used. + builder.AddOp(txscript.OP_EQUAL) + + if csvOne { + // If csvOne is true, we add a CHECKSEQUENCEVERIFY to ensure + // that the HTLC can only be claimed after at least one + // confirmation. + 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..7d86008dd --- /dev/null +++ b/assets/htlc/swapkit.go @@ -0,0 +1,452 @@ +package htlc + +import ( + "context" + + "github.com/btcsuite/btcd/btcec/v2" + "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. + Amount uint64 + + // 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) { + sortKeys := true + aggregateKey, err := input.MuSig2CombineKeys( + input.MuSig2Version100RC2, + []*btcec.PublicKey{ + s.SenderPubKey, s.ReceiverPubKey, + }, + sortKeys, + &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: 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(p *proof.Proof) ([]byte, error) { + tapCommitment, err := p.VerifyProofs() + if err != nil { + return nil, err + } + + taprootAssetRoot := tapCommitment.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..fc4338c57 --- /dev/null +++ b/assets/tapkit.go @@ -0,0 +1,120 @@ +package assets + +import ( + "context" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "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 interactively 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, + sweepScriptKey asset.ScriptKey, sweepInternalKey *btcec.PublicKey, + tapScriptSibling *commitment.TapscriptPreimage, + 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 + } + + sweepVpkt.Outputs = append(sweepVpkt.Outputs, &tappsbt.VOutput{ + AssetVersion: asset.V1, + Amount: total, + Interactive: true, + AnchorOutputIndex: 0, + ScriptKey: sweepScriptKey, + AnchorOutputInternalKey: sweepInternalKey, + AnchorOutputTapscriptSibling: tapScriptSibling, + ProofDeliveryAddress: nil, + }) + + 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) + } + + return sweepVpkt, nil +} diff --git a/cmd/loop/asset_deposits.go b/cmd/loop/asset_deposits.go new file mode 100644 index 000000000..3edc03e80 --- /dev/null +++ b/cmd/loop/asset_deposits.go @@ -0,0 +1,171 @@ +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, + }, + } + + 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.", + }, + }, + } +) + +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 +} 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 2ea7436a8..68309b6ab 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 github.com/jackc/pgconn v1.14.3 github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 - github.com/jackc/pgx/v5 v5.6.0 + github.com/jackc/pgx/v5 v5.7.4 github.com/jessevdk/go-flags v1.4.0 github.com/lib/pq v1.10.9 github.com/lightninglabs/aperture v0.3.13-beta @@ -36,7 +36,7 @@ require ( 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 @@ -73,8 +73,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 @@ -102,11 +102,11 @@ 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/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/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,7 +120,7 @@ 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 @@ -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 +// 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-temp) +replace github.com/lightninglabs/taproot-assets => github.com/bhandras/taproot-assets v0.0.0-20250817135045-e6c8a3e9bfa7 + +// 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-20250814120557-c9b71df55dd9 + go 1.23.12 + +toolchain go1.24.5 diff --git a/go.sum b/go.sum index 0e7c29591..aeea05084 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-20250817135045-e6c8a3e9bfa7 h1:V20EdDW6s+pkciheyyemD3saY/2dWRInCIJeOQ1k4tw= +github.com/bhandras/taproot-assets v0.0.0-20250817135045-e6c8a3e9bfa7/go.mod h1:FcVAHtAZmFjxc2Aos8eyuj5Tl7tWIwSYYW3dkNAsHRQ= +github.com/bhandras/taproot-assets/taprpc v0.0.0-20250814120557-c9b71df55dd9 h1:Gf5PrC4Rm0fskNutfqQ2YMk0Ocg/NpMf9ga/rCYhzxo= +github.com/bhandras/taproot-assets/taprpc v0.0.0-20250814120557-c9b71df55dd9/go.mod h1:tQx1OrBzhtGPJfBVHSWjHH9x4RPv8YOPIdnxzc7H6IU= 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-0.20250729190616-3f323918a96e h1:wlaM8dTlpCQ0uNj0TBskBDeNTTDessxiXiakYDB4RFo= -github.com/lightninglabs/taproot-assets v0.6.1-0.20250729190616-3f323918a96e/go.mod h1:mIgx0p/GkMZeEjEm91vYQsH41YQBAgJl7TP6JcT+wgs= -github.com/lightninglabs/taproot-assets/taprpc v1.0.10-0.20250729190616-3f323918a96e h1:MnXspinwkd6VhV8G9I+TdSak05UitfYyNBCQhTIIr0g= -github.com/lightninglabs/taproot-assets/taprpc v1.0.10-0.20250729190616-3f323918a96e/go.mod h1:c8gTEcKEUoUPVChgZNwqTL1hss7UWa5FDeObr8WBzQk= -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,8 +1132,6 @@ 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.10 h1:ZLV7TGwjnKupVfCd+DJ43MAc9BKVSFCnvhpSPGKdN3M= -github.com/lightningnetwork/lnd/sqldb v1.0.10/go.mod h1:c/vWoQfcxu6FAfHzGajkIQi7CEIeIZFhhH4DYh1BJpc= 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.2 h1:MO4FCk7F4k5xPMqVZF6Nb/kOpxlwPrUQpYjmyKny5s0= @@ -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 c5f1dc70b..ffe68483d 100644 --- a/loopd/daemon.go +++ b/loopd/daemon.go @@ -7,6 +7,7 @@ import ( "fmt" "net" "net/http" + _ "net/http/pprof" //nolint:gosec "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" @@ -100,6 +102,8 @@ type Daemon struct { restCtxCancel func() macaroonService *lndclient.MacaroonService + + profiler *Profiler } // New creates a new instance of the loop client daemon. @@ -132,6 +136,10 @@ func (d *Daemon) Start() error { return errOnlyStartOnce } + // TODO(bhandras): only start if enabled. Make port configurable. + d.profiler = NewProfiler(4321) + d.profiler.Start() + network := lndclient.Network(d.cfg.Network) var err error @@ -248,6 +256,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 +430,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 +513,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 +598,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. @@ -698,8 +722,25 @@ 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, @@ -718,6 +759,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. @@ -984,6 +1026,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 @@ -1070,6 +1127,13 @@ func (d *Daemon) stop() { d.clientCleanup() } + if d.profiler != nil { + err := d.profiler.Stop() + if err != nil { + errorf("Error stopping profiler: %v", err) + } + } + // Everything should be shutting down now, wait for completion. d.wg.Wait() } 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/profiler.go b/loopd/profiler.go new file mode 100644 index 000000000..cb0edfa3d --- /dev/null +++ b/loopd/profiler.go @@ -0,0 +1,102 @@ +package loopd + +import ( + "context" + "fmt" + "net/http" + "net/http/pprof" + "sync" + "time" +) + +const ( + profilerCloseTimeout = 5 * time.Second +) + +// Profiler is a wrapper around Go's net/http/pprof package which enables +// graceful startup and shutdown of the pprof server. +type Profiler struct { + srv *http.Server + done chan struct{} + once sync.Once +} + +// NewProfiler creates a new Profiler instance that listens on the specified +// port. +func NewProfiler(port int) *Profiler { + mux := http.NewServeMux() + + // Landing redirect + mux.Handle("/", http.RedirectHandler( + "/debug/pprof/", http.StatusSeeOther), + ) + + // pprof endpoints + mux.HandleFunc("/debug/pprof/", pprof.Index) + mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + mux.HandleFunc("/debug/pprof/trace", pprof.Trace) + + srv := &http.Server{ + Addr: fmt.Sprintf(":%d", port), + Handler: mux, + // Use some defailt timeouts to prevent hanging connections + // (gosec). + ReadHeaderTimeout: 5 * time.Second, + IdleTimeout: 60 * time.Second, + } + + return &Profiler{ + srv: srv, + done: make(chan struct{}), + } +} + +// Start initializes the pprof server and starts listening for requests. +func (p *Profiler) Start() { + p.once.Do(func() { + go func() { + infof("Profiler (pprof) listening on %s", p.srv.Addr) + err := p.srv.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + errorf("Profiler (pprof) server error: %v", + err) + } + + // Signal termination. + close(p.done) + }() + }) +} + +// Stop attempts a graceful shutdown and waits for completion. Pass a context +// with timeout to limit how long you’re willing to wait. +func (p *Profiler) Stop() error { + ctxt, cancel := context.WithTimeout( + context.Background(), profilerCloseTimeout, + ) + defer cancel() + + // Ask the server to gracefully shut down: stop accepting new + // connections, let in-flight handlers finish. + err := p.srv.Shutdown(ctxt) + if err != nil { + // Attempt to force close the server if shutdown fails. + err = p.srv.Close() + if err != nil { + errorf("Profiler (pprof) server close error: %v", + err) + } + } + + // Wait for the ListenAndServe goroutine to exit. + select { + case <-p.done: + case <-ctxt.Done(): + // If the caller’s context ends before goroutine exit, bail out. + return ctxt.Err() + } + + return nil +} 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..ec52ea976 --- /dev/null +++ b/loopdb/sqlc/asset_deposits.sql.go @@ -0,0 +1,323 @@ +// 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_script_pubkey, d.sweep_internal_pubkey, 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 + SweepScriptPubkey []byte + SweepInternalPubkey []byte + 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.SweepScriptPubkey, + &i.SweepInternalPubkey, + &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_script_pubkey, d.sweep_internal_pubkey, 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 + SweepScriptPubkey []byte + SweepInternalPubkey []byte + 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.SweepScriptPubkey, + &i.SweepInternalPubkey, + &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 setAssetDepositSweepKeys = `-- name: SetAssetDepositSweepKeys :exec +UPDATE asset_deposits +SET sweep_script_pubkey = $2, sweep_internal_pubkey = $3 +WHERE deposit_id = $1 +` + +type SetAssetDepositSweepKeysParams struct { + DepositID string + SweepScriptPubkey []byte + SweepInternalPubkey []byte +} + +func (q *Queries) SetAssetDepositSweepKeys(ctx context.Context, arg SetAssetDepositSweepKeysParams) error { + _, err := q.db.ExecContext(ctx, setAssetDepositSweepKeys, arg.DepositID, arg.SweepScriptPubkey, arg.SweepInternalPubkey) + 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/000018_asset_deposits.down.sql b/loopdb/sqlc/migrations/000018_asset_deposits.down.sql new file mode 100644 index 000000000..3ac57554b --- /dev/null +++ b/loopdb/sqlc/migrations/000018_asset_deposits.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS asset_deposits; diff --git a/loopdb/sqlc/migrations/000018_asset_deposits.up.sql b/loopdb/sqlc/migrations/000018_asset_deposits.up.sql new file mode 100644 index 000000000..a02f734b3 --- /dev/null +++ b/loopdb/sqlc/migrations/000018_asset_deposits.up.sql @@ -0,0 +1,101 @@ +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_script_pubkey is the script key that will be used for the asset + -- after the deposit is swept. + sweep_script_pubkey BLOB, + + -- sweep_internal_pubkey is the internal public key that will be used + -- for the asset output after the deposit is swept. + sweep_internal_pubkey BLOB +); + +-- 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 46766aca1..82a265aa9 100644 --- a/loopdb/sqlc/models.go +++ b/loopdb/sqlc/models.go @@ -9,6 +9,41 @@ 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 + SweepScriptPubkey []byte + SweepInternalPubkey []byte +} + +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 0a380e426..5d2132062 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 @@ -22,7 +23,10 @@ type Querier interface { DepositIDsForSwapHash(ctx context.Context, swapHash []byte) ([][]byte, error) DepositsForSwapHash(ctx context.Context, swapHash []byte) ([]DepositsForSwapHashRow, 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) @@ -65,10 +69,14 @@ type Querier interface { InsertSwapUpdate(ctx context.Context, arg InsertSwapUpdateParams) error IsStored(ctx context.Context, swapHash []byte) (bool, error) MapDepositToSwap(ctx context.Context, arg MapDepositToSwapParams) error + MarkDepositConfirmed(ctx context.Context, arg MarkDepositConfirmedParams) error OverrideSwapCosts(ctx context.Context, arg OverrideSwapCostsParams) error + SetAssetDepositServerInternalKey(ctx context.Context, arg SetAssetDepositServerInternalKeyParams) error + SetAssetDepositSweepKeys(ctx context.Context, arg SetAssetDepositSweepKeysParams) error SwapHashForDepositID(ctx context.Context, depositID []byte) ([]byte, 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..5e3a99141 --- /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: SetAssetDepositSweepKeys :exec +UPDATE asset_deposits +SET sweep_script_pubkey = $2, sweep_internal_pubkey = $3 +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..6b0ad3db3 --- /dev/null +++ b/looprpc/client_asset_deposit.pb.go @@ -0,0 +1,961 @@ +// 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"` + SweepScriptKey string `protobuf:"bytes,10,opt,name=sweep_script_key,json=sweepScriptKey,proto3" json:"sweep_script_key,omitempty"` + SweepInternalKey string `protobuf:"bytes,11,opt,name=sweep_internal_key,json=sweepInternalKey,proto3" json:"sweep_internal_key,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) GetSweepScriptKey() string { + if x != nil { + return x.SweepScriptKey + } + return "" +} + +func (x *AssetDeposit) GetSweepInternalKey() string { + if x != nil { + return x.SweepInternalKey + } + 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 PushAssetDepositHtlcSigRequest 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"` + PreimageHash []byte `protobuf:"bytes,2,opt,name=preimage_hash,json=preimageHash,proto3" json:"preimage_hash,omitempty"` + CsvExpiry uint32 `protobuf:"varint,3,opt,name=csv_expiry,json=csvExpiry,proto3" json:"csv_expiry,omitempty"` + Nonce []byte `protobuf:"bytes,4,opt,name=nonce,proto3" json:"nonce,omitempty"` +} + +func (x *PushAssetDepositHtlcSigRequest) Reset() { + *x = PushAssetDepositHtlcSigRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_client_asset_deposit_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushAssetDepositHtlcSigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushAssetDepositHtlcSigRequest) ProtoMessage() {} + +func (x *PushAssetDepositHtlcSigRequest) 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 PushAssetDepositHtlcSigRequest.ProtoReflect.Descriptor instead. +func (*PushAssetDepositHtlcSigRequest) Descriptor() ([]byte, []int) { + return file_client_asset_deposit_proto_rawDescGZIP(), []int{9} +} + +func (x *PushAssetDepositHtlcSigRequest) GetDepositId() string { + if x != nil { + return x.DepositId + } + return "" +} + +func (x *PushAssetDepositHtlcSigRequest) GetPreimageHash() []byte { + if x != nil { + return x.PreimageHash + } + return nil +} + +func (x *PushAssetDepositHtlcSigRequest) GetCsvExpiry() uint32 { + if x != nil { + return x.CsvExpiry + } + return 0 +} + +func (x *PushAssetDepositHtlcSigRequest) GetNonce() []byte { + if x != nil { + return x.Nonce + } + return nil +} + +type PushAssetDepositHtlcSigResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *PushAssetDepositHtlcSigResponse) Reset() { + *x = PushAssetDepositHtlcSigResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_client_asset_deposit_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushAssetDepositHtlcSigResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushAssetDepositHtlcSigResponse) ProtoMessage() {} + +func (x *PushAssetDepositHtlcSigResponse) 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 PushAssetDepositHtlcSigResponse.ProtoReflect.Descriptor instead. +func (*PushAssetDepositHtlcSigResponse) 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, 0x82, 0x03, 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, + 0x28, 0x0a, 0x10, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x5f, + 0x6b, 0x65, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x77, 0x65, 0x65, 0x70, + 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x77, 0x65, + 0x65, 0x70, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x73, 0x77, 0x65, 0x65, 0x70, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 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, 0x99, 0x01, 0x0a, 0x1e, 0x50, 0x75, + 0x73, 0x68, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x48, 0x74, + 0x6c, 0x63, 0x53, 0x69, 0x67, 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, 0x12, 0x23, 0x0a, 0x0d, 0x70, + 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x48, 0x61, 0x73, 0x68, + 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x73, 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x63, 0x73, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, + 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0x21, 0x0a, 0x1f, 0x50, 0x75, 0x73, 0x68, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x84, 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, 0x6c, 0x0a, 0x17, 0x50, 0x75, 0x73, 0x68, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x12, 0x27, 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, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 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, 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 + (*PushAssetDepositHtlcSigRequest)(nil), // 9: looprpc.PushAssetDepositHtlcSigRequest + (*PushAssetDepositHtlcSigResponse)(nil), // 10: looprpc.PushAssetDepositHtlcSigResponse +} +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.PushAssetDepositHtlcSig:input_type -> looprpc.PushAssetDepositHtlcSigRequest + 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.PushAssetDepositHtlcSig:output_type -> looprpc.PushAssetDepositHtlcSigResponse + 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.(*PushAssetDepositHtlcSigRequest); 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.(*PushAssetDepositHtlcSigResponse); 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..2173fafb1 --- /dev/null +++ b/looprpc/client_asset_deposit.proto @@ -0,0 +1,100 @@ +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 PushAssetDepositHtlcSig (PushAssetDepositHtlcSigRequest) + returns (PushAssetDepositHtlcSigResponse); +} + +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_script_key = 10; + + string sweep_internal_key = 11; +} + +message WithdrawAssetDepositsRequest { + repeated string deposit_ids = 1; +} + +message WithdrawAssetDepositsResponse { +} + +message RevealAssetDepositKeyRequest { + string deposit_id = 1; +} + +message RevealAssetDepositKeyResponse { +} + +message PushAssetDepositHtlcSigRequest { + string deposit_id = 1; + + bytes preimage_hash = 2; + + uint32 csv_expiry = 3; + + bytes nonce = 4; +} + +message PushAssetDepositHtlcSigResponse { +} diff --git a/looprpc/client_asset_deposit_grpc.pb.go b/looprpc/client_asset_deposit_grpc.pb.go new file mode 100644 index 000000000..d4fd2443b --- /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) + PushAssetDepositHtlcSig(ctx context.Context, in *PushAssetDepositHtlcSigRequest, opts ...grpc.CallOption) (*PushAssetDepositHtlcSigResponse, 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) PushAssetDepositHtlcSig(ctx context.Context, in *PushAssetDepositHtlcSigRequest, opts ...grpc.CallOption) (*PushAssetDepositHtlcSigResponse, error) { + out := new(PushAssetDepositHtlcSigResponse) + err := c.cc.Invoke(ctx, "/looprpc.AssetDepositClient/PushAssetDepositHtlcSig", 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) + PushAssetDepositHtlcSig(context.Context, *PushAssetDepositHtlcSigRequest) (*PushAssetDepositHtlcSigResponse, 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) PushAssetDepositHtlcSig(context.Context, *PushAssetDepositHtlcSigRequest) (*PushAssetDepositHtlcSigResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PushAssetDepositHtlcSig 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_PushAssetDepositHtlcSig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PushAssetDepositHtlcSigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetDepositClientServer).PushAssetDepositHtlcSig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetDepositClient/PushAssetDepositHtlcSig", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetDepositClientServer).PushAssetDepositHtlcSig(ctx, req.(*PushAssetDepositHtlcSigRequest)) + } + 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: "PushAssetDepositHtlcSig", + Handler: _AssetDepositClient_PushAssetDepositHtlcSig_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "client_asset_deposit.proto", +} diff --git a/looprpc/perms.go b/looprpc/perms.go index be28d7c8d..4ba5f0cc4 100644 --- a/looprpc/perms.go +++ b/looprpc/perms.go @@ -176,4 +176,28 @@ 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", + }}, + "/looprpc.AssetDepositClient/PushAssetDepositHtlcSig": {{ + Entity: "swap", + Action: "execute", + }}, } diff --git a/swapserverrpc/asset_deposit.pb.go b/swapserverrpc/asset_deposit.pb.go new file mode 100644 index 000000000..94be837aa --- /dev/null +++ b/swapserverrpc/asset_deposit.pb.go @@ -0,0 +1,1058 @@ +// 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 +} + +// InitAssetDepositSigningSessionReq is the request to the Server to initialize +// a signing session for one or more deposits. The Server will return a list of +// nonces that the client must use to sign the deposits. +type InitAssetDepositSigningSessionReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // deposit_ids is a list of deposit IDs to sign. + DepositIds []string `protobuf:"bytes,1,rep,name=deposit_ids,json=depositIds,proto3" json:"deposit_ids,omitempty"` +} + +func (x *InitAssetDepositSigningSessionReq) Reset() { + *x = InitAssetDepositSigningSessionReq{} + if protoimpl.UnsafeEnabled { + mi := &file_asset_deposit_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *InitAssetDepositSigningSessionReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InitAssetDepositSigningSessionReq) ProtoMessage() {} + +func (x *InitAssetDepositSigningSessionReq) 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 InitAssetDepositSigningSessionReq.ProtoReflect.Descriptor instead. +func (*InitAssetDepositSigningSessionReq) Descriptor() ([]byte, []int) { + return file_asset_deposit_proto_rawDescGZIP(), []int{5} +} + +func (x *InitAssetDepositSigningSessionReq) GetDepositIds() []string { + if x != nil { + return x.DepositIds + } + return nil +} + +// InitDepositSigningSessionRes contains a list of nonces that the client must +// use to sign the deposits. +type InitAssetDepositSigningSessionRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // nonces is a map which maps deposit_id to nonce. The client can use these + // nonces at most once to sign the deposits. The server may revoke nonces + // in which case the client needs to re-initiate the signing session. + Nonces map[string][]byte `protobuf:"bytes,1,rep,name=nonces,proto3" json:"nonces,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *InitAssetDepositSigningSessionRes) Reset() { + *x = InitAssetDepositSigningSessionRes{} + if protoimpl.UnsafeEnabled { + mi := &file_asset_deposit_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *InitAssetDepositSigningSessionRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InitAssetDepositSigningSessionRes) ProtoMessage() {} + +func (x *InitAssetDepositSigningSessionRes) 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 InitAssetDepositSigningSessionRes.ProtoReflect.Descriptor instead. +func (*InitAssetDepositSigningSessionRes) Descriptor() ([]byte, []int) { + return file_asset_deposit_proto_rawDescGZIP(), []int{6} +} + +func (x *InitAssetDepositSigningSessionRes) GetNonces() map[string][]byte { + if x != nil { + return x.Nonces + } + 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 + + // hash is the hash used in the HTLC script. + Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` + // csv_expiry is the CSV expiry for the HTLC. + CsvExpiry uint32 `protobuf:"varint,2,opt,name=csv_expiry,json=csvExpiry,proto3" json:"csv_expiry,omitempty"` + // 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,3,rep,name=partial_sigs,json=partialSigs,proto3" json:"partial_sigs,omitempty"` + // htlc_psbt is the HTLC psbt. + HtlcPsbt []byte `protobuf:"bytes,4,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[7] + 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[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 PushAssetDepositHtlcSigsReq.ProtoReflect.Descriptor instead. +func (*PushAssetDepositHtlcSigsReq) Descriptor() ([]byte, []int) { + return file_asset_deposit_proto_rawDescGZIP(), []int{7} +} + +func (x *PushAssetDepositHtlcSigsReq) GetHash() []byte { + if x != nil { + return x.Hash + } + return nil +} + +func (x *PushAssetDepositHtlcSigsReq) GetCsvExpiry() uint32 { + if x != nil { + return x.CsvExpiry + } + return 0 +} + +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[8] + 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[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 PushAssetDepositHtlcSigsRes.ProtoReflect.Descriptor instead. +func (*PushAssetDepositHtlcSigsRes) Descriptor() ([]byte, []int) { + return file_asset_deposit_proto_rawDescGZIP(), []int{8} +} + +// 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[9] + 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[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 PushAssetDepositKeysReq.ProtoReflect.Descriptor instead. +func (*PushAssetDepositKeysReq) Descriptor() ([]byte, []int) { + return file_asset_deposit_proto_rawDescGZIP(), []int{9} +} + +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[10] + 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[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 PushAssetDepositKeysRes.ProtoReflect.Descriptor instead. +func (*PushAssetDepositKeysRes) Descriptor() ([]byte, []int) { + return file_asset_deposit_proto_rawDescGZIP(), []int{10} +} + +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, + 0x44, 0x0a, 0x21, 0x49, 0x6e, 0x69, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, + 0x73, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 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, 0xae, 0x01, 0x0a, 0x21, 0x49, 0x6e, 0x69, 0x74, 0x41, 0x73, + 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, + 0x67, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x12, 0x4e, 0x0a, 0x06, 0x6e, + 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x6c, 0x6f, + 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, + 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x2e, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x06, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4e, + 0x6f, 0x6e, 0x63, 0x65, 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, 0xb1, 0x01, 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, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x73, + 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, + 0x63, 0x73, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x42, 0x0a, 0x0c, 0x70, 0x61, 0x72, + 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x73, 0x18, 0x03, 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, 0x04, 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, 0x97, 0x04, 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, 0x78, 0x0a, 0x1e, 0x49, 0x6e, 0x69, 0x74, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x69, + 0x6e, 0x67, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, + 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x2a, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, + 0x49, 0x6e, 0x69, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, + 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 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, 14) +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 + (*InitAssetDepositSigningSessionReq)(nil), // 6: looprpc.InitAssetDepositSigningSessionReq + (*InitAssetDepositSigningSessionRes)(nil), // 7: looprpc.InitAssetDepositSigningSessionRes + (*PushAssetDepositHtlcSigsReq)(nil), // 8: looprpc.PushAssetDepositHtlcSigsReq + (*PushAssetDepositHtlcSigsRes)(nil), // 9: looprpc.PushAssetDepositHtlcSigsRes + (*PushAssetDepositKeysReq)(nil), // 10: looprpc.PushAssetDepositKeysReq + (*PushAssetDepositKeysRes)(nil), // 11: looprpc.PushAssetDepositKeysRes + nil, // 12: looprpc.WithdrawAssetDepositsServerRes.DepositKeysEntry + nil, // 13: looprpc.InitAssetDepositSigningSessionRes.NoncesEntry + nil, // 14: looprpc.PushAssetDepositKeysReq.DepositKeysEntry +} +var file_asset_deposit_proto_depIdxs = []int32{ + 12, // 0: looprpc.WithdrawAssetDepositsServerRes.deposit_keys:type_name -> looprpc.WithdrawAssetDepositsServerRes.DepositKeysEntry + 13, // 1: looprpc.InitAssetDepositSigningSessionRes.nonces:type_name -> looprpc.InitAssetDepositSigningSessionRes.NoncesEntry + 5, // 2: looprpc.PushAssetDepositHtlcSigsReq.partial_sigs:type_name -> looprpc.AssetDepositPartialSig + 14, // 3: looprpc.PushAssetDepositKeysReq.deposit_keys:type_name -> looprpc.PushAssetDepositKeysReq.DepositKeysEntry + 1, // 4: looprpc.AssetDepositService.NewAssetDeposit:input_type -> looprpc.NewAssetDepositServerReq + 3, // 5: looprpc.AssetDepositService.WithdrawAssetDeposits:input_type -> looprpc.WithdrawAssetDepositsServerReq + 6, // 6: looprpc.AssetDepositService.InitAssetDepositSigningSession:input_type -> looprpc.InitAssetDepositSigningSessionReq + 8, // 7: looprpc.AssetDepositService.PushAssetDepositHtlcSigs:input_type -> looprpc.PushAssetDepositHtlcSigsReq + 10, // 8: looprpc.AssetDepositService.PushAssetDepositKeys:input_type -> looprpc.PushAssetDepositKeysReq + 2, // 9: looprpc.AssetDepositService.NewAssetDeposit:output_type -> looprpc.NewAssetDepositServerRes + 4, // 10: looprpc.AssetDepositService.WithdrawAssetDeposits:output_type -> looprpc.WithdrawAssetDepositsServerRes + 7, // 11: looprpc.AssetDepositService.InitAssetDepositSigningSession:output_type -> looprpc.InitAssetDepositSigningSessionRes + 9, // 12: looprpc.AssetDepositService.PushAssetDepositHtlcSigs:output_type -> looprpc.PushAssetDepositHtlcSigsRes + 11, // 13: looprpc.AssetDepositService.PushAssetDepositKeys:output_type -> looprpc.PushAssetDepositKeysRes + 9, // [9:14] is the sub-list for method output_type + 4, // [4:9] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] 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.(*InitAssetDepositSigningSessionReq); 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.(*InitAssetDepositSigningSessionRes); 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.(*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[8].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[9].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[10].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: 14, + 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..ca125fb68 --- /dev/null +++ b/swapserverrpc/asset_deposit.proto @@ -0,0 +1,151 @@ +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); + + // InitAssetDepositSigningSession initializes a signing session for one or + // more deposits. The Server will return a list of nonces that the client + // may use at most once to sign the deposits. The server may revoke nonces, + // in which case the client needs to re-initiate the signing session. + rpc InitAssetDepositSigningSession (InitAssetDepositSigningSessionReq) + returns (InitAssetDepositSigningSessionRes); + + // 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; +} + +// InitAssetDepositSigningSessionReq is the request to the Server to initialize +// a signing session for one or more deposits. The Server will return a list of +// nonces that the client must use to sign the deposits. +message InitAssetDepositSigningSessionReq { + // deposit_ids is a list of deposit IDs to sign. + repeated string deposit_ids = 1; +} + +// InitDepositSigningSessionRes contains a list of nonces that the client must +// use to sign the deposits. +message InitAssetDepositSigningSessionRes { + // nonces is a map which maps deposit_id to nonce. The client can use these + // nonces at most once to sign the deposits. The server may revoke nonces + // in which case the client needs to re-initiate the signing session. + map nonces = 1; +} + +// PushAssetDepositHtlcSigsReq holds partial signatures spending one or more +// deposits and the zero fee HTLC spending them. +message PushAssetDepositHtlcSigsReq { + // hash is the hash used in the HTLC script. + bytes hash = 1; + + // csv_expiry is the CSV expiry for the HTLC. + uint32 csv_expiry = 2; + + // 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 = 3; + + // htlc_psbt is the HTLC psbt. + bytes htlc_psbt = 4; +} + +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..5a29a4086 --- /dev/null +++ b/swapserverrpc/asset_deposit_grpc.pb.go @@ -0,0 +1,265 @@ +// 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) + // InitAssetDepositSigningSession initializes a signing session for one or + // more deposits. The Server will return a list of nonces that the client + // may use at most once to sign the deposits. The server may revoke nonces, + // in which case the client needs to re-initiate the signing session. + InitAssetDepositSigningSession(ctx context.Context, in *InitAssetDepositSigningSessionReq, opts ...grpc.CallOption) (*InitAssetDepositSigningSessionRes, 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) InitAssetDepositSigningSession(ctx context.Context, in *InitAssetDepositSigningSessionReq, opts ...grpc.CallOption) (*InitAssetDepositSigningSessionRes, error) { + out := new(InitAssetDepositSigningSessionRes) + err := c.cc.Invoke(ctx, "/looprpc.AssetDepositService/InitAssetDepositSigningSession", 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) + // InitAssetDepositSigningSession initializes a signing session for one or + // more deposits. The Server will return a list of nonces that the client + // may use at most once to sign the deposits. The server may revoke nonces, + // in which case the client needs to re-initiate the signing session. + InitAssetDepositSigningSession(context.Context, *InitAssetDepositSigningSessionReq) (*InitAssetDepositSigningSessionRes, 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) InitAssetDepositSigningSession(context.Context, *InitAssetDepositSigningSessionReq) (*InitAssetDepositSigningSessionRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method InitAssetDepositSigningSession 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_InitAssetDepositSigningSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(InitAssetDepositSigningSessionReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetDepositServiceServer).InitAssetDepositSigningSession(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetDepositService/InitAssetDepositSigningSession", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetDepositServiceServer).InitAssetDepositSigningSession(ctx, req.(*InitAssetDepositSigningSessionReq)) + } + 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: "InitAssetDepositSigningSession", + Handler: _AssetDepositService_InitAssetDepositSigningSession_Handler, + }, + { + MethodName: "PushAssetDepositHtlcSigs", + Handler: _AssetDepositService_PushAssetDepositHtlcSigs_Handler, + }, + { + MethodName: "PushAssetDepositKeys", + Handler: _AssetDepositService_PushAssetDepositKeys_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "asset_deposit.proto", +}