Skip to content

Commit d3b34d2

Browse files
author
ffranr
authored
Merge pull request #1402 from lightninglabs/group-key-refactor
[wallet 1/3]: refactor in preparation for group key channel funding
2 parents 70d886b + d416a6f commit d3b34d2

17 files changed

+1407
-673
lines changed

fn/memory.go

+32
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
package fn
22

3+
import "errors"
4+
5+
var (
6+
// ErrNilPointerDeference is returned when a nil pointer is
7+
// dereferenced.
8+
ErrNilPointerDeference = errors.New("nil pointer dereference")
9+
)
10+
311
// Ptr returns the pointer of the given value. This is useful in instances
412
// where a function returns the value, but a pointer is wanted. Without this,
513
// then an intermediate variable is needed.
@@ -32,7 +40,31 @@ func ToArray[T ByteArray](v []byte) T {
3240
// CopySlice returns a copy of the given slice. Does a shallow copy of the
3341
// slice itself, not the underlying elements.
3442
func CopySlice[T any](slice []T) []T {
43+
if slice == nil {
44+
return nil
45+
}
46+
3547
newSlice := make([]T, len(slice))
3648
copy(newSlice, slice)
3749
return newSlice
3850
}
51+
52+
// Deref safely dereferences a pointer. If the pointer is nil, it returns the
53+
// zero value of type T and an error.
54+
func Deref[T any](ptr *T) (T, error) {
55+
if ptr == nil {
56+
var zero T
57+
return zero, ErrNilPointerDeference
58+
}
59+
60+
return *ptr, nil
61+
}
62+
63+
// DerefPanic dereferences a pointer. If the pointer is nil, it panics.
64+
func DerefPanic[T any](ptr *T) T {
65+
if ptr == nil {
66+
panic(ErrNilPointerDeference)
67+
}
68+
69+
return *ptr
70+
}

key_ring.go

-18
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"fmt"
66

7-
"github.com/davecgh/go-spew/spew"
87
"github.com/lightninglabs/lndclient"
98
"github.com/lightninglabs/taproot-assets/asset"
109
"github.com/lightninglabs/taproot-assets/tapgarden"
@@ -60,23 +59,6 @@ func (l *LndRpcKeyRing) DeriveNextTaprootAssetKey(
6059
return *keyDesc, nil
6160
}
6261

63-
// DeriveKey attempts to derive an arbitrary key specified by the passed
64-
// KeyLocator. This may be used in several recovery scenarios, or when manually
65-
// rotating something like our current default node key.
66-
func (l *LndRpcKeyRing) DeriveKey(ctx context.Context,
67-
keyLoc keychain.KeyLocator) (keychain.KeyDescriptor, error) {
68-
69-
tapdLog.Debugf("Deriving new key, key_loc=%v", spew.Sdump(keyLoc))
70-
71-
keyDesc, err := l.lnd.WalletKit.DeriveKey(ctx, &keyLoc)
72-
if err != nil {
73-
return keychain.KeyDescriptor{}, fmt.Errorf("unable to "+
74-
"derive key ring: %w", err)
75-
}
76-
77-
return *keyDesc, nil
78-
}
79-
8062
// IsLocalKey returns true if the key is under the control of the wallet
8163
// and can be derived by it.
8264
func (l *LndRpcKeyRing) IsLocalKey(ctx context.Context,

proof/archive.go

+8-3
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,8 @@ type AnnotatedProof struct {
133133
*AssetSnapshot
134134
}
135135

136-
// Archiver is the main storage backend the ProofArchiver uses to store and
137-
// query for proof files.
138-
type Archiver interface {
136+
// Exporter is used to fetch proofs by their unique identifier.
137+
type Exporter interface {
139138
// FetchProof fetches a proof for an asset uniquely identified by the
140139
// passed ProofIdentifier.
141140
//
@@ -144,6 +143,12 @@ type Archiver interface {
144143
// locator then ErrMultipleProofs should be returned to indicate more
145144
// specific fields need to be set in the Locator (e.g. the OutPoint).
146145
FetchProof(ctx context.Context, id Locator) (Blob, error)
146+
}
147+
148+
// Archiver is the main storage backend the ProofArchiver uses to store and
149+
// query for proof files.
150+
type Archiver interface {
151+
Exporter
147152

148153
// FetchIssuanceProof fetches the issuance proof for an asset, given the
149154
// anchor point of the issuance (NOT the genesis point for the asset).

rpcserver.go

+98-32
Original file line numberDiff line numberDiff line change
@@ -2235,8 +2235,7 @@ func (r *rpcServer) FundVirtualPsbt(ctx context.Context,
22352235
// Extract the passive assets that are needed for the fully RPC driven
22362236
// flow.
22372237
passivePackets, err := r.cfg.AssetWallet.CreatePassiveAssets(
2238-
ctx, []*tappsbt.VPacket{fundedVPkt.VPacket},
2239-
fundedVPkt.InputCommitments,
2238+
ctx, fundedVPkt.VPackets, fundedVPkt.InputCommitments,
22402239
)
22412240
if err != nil {
22422241
return nil, fmt.Errorf("error creating passive assets: %w", err)
@@ -2257,7 +2256,12 @@ func (r *rpcServer) FundVirtualPsbt(ctx context.Context,
22572256
}
22582257
}
22592258

2260-
response.FundedPsbt, err = serialize(fundedVPkt.VPacket)
2259+
// TODO(guggero): Remove this once we support multiple packets.
2260+
if len(fundedVPkt.VPackets) > 1 {
2261+
return nil, fmt.Errorf("only one packet supported")
2262+
}
2263+
2264+
response.FundedPsbt, err = serialize(fundedVPkt.VPackets[0])
22612265
if err != nil {
22622266
return nil, fmt.Errorf("error serializing packet: %w", err)
22632267
}
@@ -3387,11 +3391,13 @@ func (r *rpcServer) BurnAsset(ctx context.Context,
33873391
// We found the asset group, so we can use the group key to
33883392
// burn the asset.
33893393
groupKey = &assetGroup.GroupPubKey
3394+
33903395
case errors.Is(err, address.ErrAssetGroupUnknown):
33913396
// We don't know the asset group, so we'll try to burn the
33923397
// asset using the asset ID only.
33933398
rpcsLog.Debug("Asset group key not found, asset may not be " +
33943399
"part of a group")
3400+
33953401
case err != nil:
33963402
return nil, fmt.Errorf("error querying asset group: %w", err)
33973403
}
@@ -3419,16 +3425,22 @@ func (r *rpcServer) BurnAsset(ctx context.Context,
34193425
return nil, fmt.Errorf("error funding burn: %w", err)
34203426
}
34213427

3428+
// We don't support burning by group key yet, so we only expect a single
3429+
// vPacket (which implies a single asset ID is involved).
3430+
if len(fundResp.VPackets) > 1 {
3431+
return nil, fmt.Errorf("only one packet supported")
3432+
}
3433+
34223434
// Now we can sign the packet and send it to the chain.
3423-
_, err = r.cfg.AssetWallet.SignVirtualPacket(fundResp.VPacket)
3435+
vPkt := fundResp.VPackets[0]
3436+
_, err = r.cfg.AssetWallet.SignVirtualPacket(vPkt)
34243437
if err != nil {
34253438
return nil, fmt.Errorf("error signing packet: %w", err)
34263439
}
34273440

34283441
resp, err := r.cfg.ChainPorter.RequestShipment(
34293442
tapfreighter.NewPreSignedParcel(
3430-
[]*tappsbt.VPacket{fundResp.VPacket},
3431-
fundResp.InputCommitments, in.Note,
3443+
fundResp.VPackets, fundResp.InputCommitments, in.Note,
34323444
),
34333445
)
34343446
if err != nil {
@@ -3443,7 +3455,7 @@ func (r *rpcServer) BurnAsset(ctx context.Context,
34433455

34443456
var burnProof *taprpc.DecodedProof
34453457
for idx := range resp.Outputs {
3446-
vOut := fundResp.VPacket.Outputs[idx]
3458+
vOut := vPkt.Outputs[idx]
34473459
tOut := resp.Outputs[idx]
34483460
if vOut.Asset.IsBurn() {
34493461
p, err := proof.Decode(tOut.ProofSuffix)
@@ -6427,54 +6439,71 @@ func MarshalAssetFedSyncCfg(
64276439
}
64286440

64296441
// unmarshalAssetSpecifier unmarshals an asset specifier from the RPC form.
6430-
func unmarshalAssetSpecifier(req *rfqrpc.AssetSpecifier) (*asset.ID,
6442+
func unmarshalAssetSpecifier(s *rfqrpc.AssetSpecifier) (*asset.ID,
64316443
*btcec.PublicKey, error) {
64326444

6445+
if s == nil {
6446+
return nil, nil, fmt.Errorf("asset specifier must be specified")
6447+
}
6448+
6449+
return parseAssetSpecifier(
6450+
s.GetAssetId(), s.GetAssetIdStr(), s.GetGroupKey(),
6451+
s.GetGroupKeyStr(),
6452+
)
6453+
}
6454+
6455+
// parseAssetSpecifier parses an asset specifier from the RPC form.
6456+
func parseAssetSpecifier(reqAssetID []byte, reqAssetIDStr string,
6457+
reqGroupKey []byte, reqGroupKeyStr string) (*asset.ID, *btcec.PublicKey,
6458+
error) {
6459+
64336460
// Attempt to decode the asset specifier from the RPC request. In cases
64346461
// where both the asset ID and asset group key are provided, we will
64356462
// give precedence to the asset ID due to its higher level of
64366463
// specificity.
64376464
var (
6438-
assetID *asset.ID
6439-
6440-
groupKeyBytes []byte
6441-
groupKey *btcec.PublicKey
6442-
6443-
err error
6465+
assetID *asset.ID
6466+
groupKey *btcec.PublicKey
6467+
err error
64446468
)
64456469

64466470
switch {
64476471
// Parse the asset ID if it's set.
6448-
case len(req.GetAssetId()) > 0:
6472+
case len(reqAssetID) > 0:
6473+
if len(reqAssetID) != sha256.Size {
6474+
return nil, nil, fmt.Errorf("asset ID must be 32 bytes")
6475+
}
6476+
64496477
var assetIdBytes [32]byte
6450-
copy(assetIdBytes[:], req.GetAssetId())
6478+
copy(assetIdBytes[:], reqAssetID)
64516479
id := asset.ID(assetIdBytes)
64526480
assetID = &id
64536481

6454-
case len(req.GetAssetIdStr()) > 0:
6455-
assetIDBytes, err := hex.DecodeString(req.GetAssetIdStr())
6482+
case len(reqAssetIDStr) > 0:
6483+
assetIDBytes, err := hex.DecodeString(reqAssetIDStr)
64566484
if err != nil {
64576485
return nil, nil, fmt.Errorf("error decoding asset "+
64586486
"ID: %w", err)
64596487
}
64606488

6489+
if len(assetIDBytes) != sha256.Size {
6490+
return nil, nil, fmt.Errorf("asset ID must be 32 bytes")
6491+
}
6492+
64616493
var id asset.ID
64626494
copy(id[:], assetIDBytes)
64636495
assetID = &id
64646496

64656497
// Parse the group key if it's set.
6466-
case len(req.GetGroupKey()) > 0:
6467-
groupKeyBytes = req.GetGroupKey()
6468-
groupKey, err = btcec.ParsePubKey(groupKeyBytes)
6498+
case len(reqGroupKey) > 0:
6499+
groupKey, err = btcec.ParsePubKey(reqGroupKey)
64696500
if err != nil {
64706501
return nil, nil, fmt.Errorf("error parsing group "+
64716502
"key: %w", err)
64726503
}
64736504

6474-
case len(req.GetGroupKeyStr()) > 0:
6475-
groupKeyBytes, err := hex.DecodeString(
6476-
req.GetGroupKeyStr(),
6477-
)
6505+
case len(reqGroupKeyStr) > 0:
6506+
groupKeyBytes, err := hex.DecodeString(reqGroupKeyStr)
64786507
if err != nil {
64796508
return nil, nil, fmt.Errorf("error decoding group "+
64806509
"key: %w", err)
@@ -7055,8 +7084,21 @@ func (r *rpcServer) FundChannel(ctx context.Context,
70557084
return nil, fmt.Errorf("error parsing peer pubkey: %w", err)
70567085
}
70577086

7058-
if len(req.AssetId) != sha256.Size {
7059-
return nil, fmt.Errorf("asset ID must be 32 bytes")
7087+
assetID, groupKey, err := parseAssetSpecifier(
7088+
req.GetAssetId(), "", nil, "",
7089+
)
7090+
if err != nil {
7091+
return nil, fmt.Errorf("error parsing asset specifier: %w", err)
7092+
}
7093+
7094+
// For channel funding, we need to make sure that the group key is set
7095+
// if the asset is grouped.
7096+
assetSpecifier, err := r.specifierWithGroupKeyLookup(
7097+
ctx, assetID, groupKey,
7098+
)
7099+
if err != nil {
7100+
return nil, fmt.Errorf("error creating asset specifier: %w",
7101+
err)
70607102
}
70617103

70627104
if req.AssetAmount == 0 {
@@ -7067,12 +7109,12 @@ func (r *rpcServer) FundChannel(ctx context.Context,
70677109
}
70687110

70697111
fundReq := tapchannel.FundReq{
7070-
PeerPub: *peerPub,
7071-
AssetAmount: req.AssetAmount,
7072-
FeeRate: chainfee.SatPerVByte(req.FeeRateSatPerVbyte),
7073-
PushAmount: btcutil.Amount(req.PushSat),
7112+
PeerPub: *peerPub,
7113+
AssetSpecifier: assetSpecifier,
7114+
AssetAmount: req.AssetAmount,
7115+
FeeRate: chainfee.SatPerVByte(req.FeeRateSatPerVbyte),
7116+
PushAmount: btcutil.Amount(req.PushSat),
70747117
}
7075-
copy(fundReq.AssetID[:], req.AssetId)
70767118

70777119
chanPoint, err := r.cfg.AuxFundingController.FundChannel(ctx, fundReq)
70787120
if err != nil {
@@ -7085,6 +7127,30 @@ func (r *rpcServer) FundChannel(ctx context.Context,
70857127
}, nil
70867128
}
70877129

7130+
// specifierWithGroupKeyLookup returns an asset specifier that has the group key
7131+
// set if it's a grouped asset.
7132+
func (r *rpcServer) specifierWithGroupKeyLookup(ctx context.Context,
7133+
assetID *asset.ID, groupKey *btcec.PublicKey) (asset.Specifier, error) {
7134+
7135+
var result asset.Specifier
7136+
7137+
if assetID != nil && groupKey == nil {
7138+
dbGroupKey, err := r.cfg.TapAddrBook.QueryAssetGroup(
7139+
ctx, *assetID,
7140+
)
7141+
switch {
7142+
case err == nil && dbGroupKey.GroupKey != nil:
7143+
groupKey = &dbGroupKey.GroupPubKey
7144+
7145+
case err != nil:
7146+
return result, fmt.Errorf("unable to query asset "+
7147+
"group: %w", err)
7148+
}
7149+
}
7150+
7151+
return asset.NewSpecifier(assetID, groupKey, nil, true)
7152+
}
7153+
70887154
// EncodeCustomRecords allows RPC users to encode Taproot Asset channel related
70897155
// data into the TLV format that is used in the custom records of the lnd
70907156
// payment or other channel related RPCs. This RPC is completely stateless and

tapcfg/server.go

+16-16
Original file line numberDiff line numberDiff line change
@@ -556,22 +556,22 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger,
556556
ProofUpdates: proofArchive,
557557
ErrChan: mainErrChan,
558558
}),
559-
AssetCustodian: tapgarden.NewCustodian(
560-
&tapgarden.CustodianConfig{
561-
ChainParams: &tapChainParams,
562-
WalletAnchor: walletAnchor,
563-
ChainBridge: chainBridge,
564-
GroupVerifier: tapgarden.GenGroupVerifier(
565-
context.Background(), assetMintingStore,
566-
),
567-
AddrBook: addrBook,
568-
ProofArchive: proofArchive,
569-
ProofNotifier: multiNotifier,
570-
ErrChan: mainErrChan,
571-
ProofCourierDispatcher: proofCourierDispatcher,
572-
ProofRetrievalDelay: cfg.CustodianProofRetrievalDelay, ProofWatcher: reOrgWatcher,
573-
},
574-
),
559+
// nolint: lll
560+
AssetCustodian: tapgarden.NewCustodian(&tapgarden.CustodianConfig{
561+
ChainParams: &tapChainParams,
562+
WalletAnchor: walletAnchor,
563+
ChainBridge: chainBridge,
564+
GroupVerifier: tapgarden.GenGroupVerifier(
565+
context.Background(), assetMintingStore,
566+
),
567+
AddrBook: addrBook,
568+
ProofArchive: proofArchive,
569+
ProofNotifier: multiNotifier,
570+
ErrChan: mainErrChan,
571+
ProofCourierDispatcher: proofCourierDispatcher,
572+
ProofRetrievalDelay: cfg.CustodianProofRetrievalDelay,
573+
ProofWatcher: reOrgWatcher,
574+
}),
575575
ChainBridge: chainBridge,
576576
AddrBook: addrBook,
577577
AddrBookDisableSyncer: cfg.AddrBook.DisableSyncer,

0 commit comments

Comments
 (0)