Skip to content

Commit b154a99

Browse files
authored
Merge pull request #865 from lightninglabs/add-asset-specifier
Add asset specifier
2 parents 4a11fd1 + 4af6579 commit b154a99

File tree

11 files changed

+244
-88
lines changed

11 files changed

+244
-88
lines changed

address/address.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,11 @@ func (a *Tap) AttachGenesis(gen asset.Genesis) {
276276
// TapCommitmentKey is the key that maps to the root commitment for the asset
277277
// group specified by a Taproot Asset address.
278278
func (a *Tap) TapCommitmentKey() [32]byte {
279-
return asset.TapCommitmentKey(a.AssetID, a.GroupKey)
279+
assetSpecifier := asset.NewSpecifierOptionalGroupPubKey(
280+
a.AssetID, a.GroupKey,
281+
)
282+
283+
return asset.TapCommitmentKey(assetSpecifier)
280284
}
281285

282286
// AssetCommitmentKey is the key that maps to the asset leaf for the asset

asset/asset.go

Lines changed: 150 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,10 @@ const (
9393
type EncodeType uint8
9494

9595
const (
96-
// Encode normal is the normal encoding type for an asset.
96+
// EncodeNormal normal is the normal encoding type for an asset.
9797
EncodeNormal EncodeType = iota
9898

99-
// EncodeSegwit denotes that the witness vector field is not be be
99+
// EncodeSegwit denotes that the witness vector field is not to be
100100
// encoded.
101101
EncodeSegwit
102102
)
@@ -247,6 +247,124 @@ func DecodeGenesis(r io.Reader) (Genesis, error) {
247247
return gen, err
248248
}
249249

250+
var (
251+
// ErrUnwrapAssetID is an error type which is returned when an asset ID
252+
// cannot be unwrapped from a specifier.
253+
ErrUnwrapAssetID = errors.New("unable to unwrap asset ID")
254+
)
255+
256+
// Specifier is a type that can be used to specify an asset by its ID, its asset
257+
// group public key, or both.
258+
type Specifier struct {
259+
// id is the asset ID.
260+
id fn.Option[ID]
261+
262+
// groupKey is the asset group public key.
263+
groupKey fn.Option[btcec.PublicKey]
264+
}
265+
266+
// NewSpecifierOptionalGroupPubKey creates a new specifier that specifies an
267+
// asset by its ID and an optional group public key.
268+
func NewSpecifierOptionalGroupPubKey(id ID,
269+
groupPubKey *btcec.PublicKey) Specifier {
270+
271+
s := Specifier{
272+
id: fn.Some(id),
273+
}
274+
275+
if groupPubKey != nil {
276+
s.groupKey = fn.Some(*groupPubKey)
277+
}
278+
279+
return s
280+
}
281+
282+
// NewSpecifierOptionalGroupKey creates a new specifier that specifies an
283+
// asset by its ID and an optional group key.
284+
func NewSpecifierOptionalGroupKey(id ID, groupKey *GroupKey) Specifier {
285+
s := Specifier{
286+
id: fn.Some(id),
287+
}
288+
289+
if groupKey != nil {
290+
s.groupKey = fn.Some(groupKey.GroupPubKey)
291+
}
292+
293+
return s
294+
}
295+
296+
// NewSpecifierFromId creates a new specifier that specifies an asset by its ID.
297+
func NewSpecifierFromId(id ID) Specifier {
298+
return Specifier{
299+
id: fn.Some(id),
300+
}
301+
}
302+
303+
// NewSpecifierFromGroupKey creates a new specifier that specifies an asset by
304+
// its group public key.
305+
func NewSpecifierFromGroupKey(groupPubKey btcec.PublicKey) Specifier {
306+
return Specifier{
307+
groupKey: fn.Some(groupPubKey),
308+
}
309+
}
310+
311+
// AsBytes returns the asset ID and group public key as byte slices.
312+
func (s *Specifier) AsBytes() ([]byte, []byte) {
313+
var assetIDBytes, groupKeyBytes []byte
314+
315+
s.WhenGroupPubKey(func(groupKey btcec.PublicKey) {
316+
groupKeyBytes = groupKey.SerializeCompressed()
317+
})
318+
319+
s.WhenId(func(id ID) {
320+
assetIDBytes = id[:]
321+
})
322+
323+
return assetIDBytes, groupKeyBytes
324+
}
325+
326+
// HasId returns true if the asset ID field is specified.
327+
func (s *Specifier) HasId() bool {
328+
return s.id.IsSome()
329+
}
330+
331+
// HasGroupPubKey returns true if the asset group public key field is specified.
332+
func (s *Specifier) HasGroupPubKey() bool {
333+
return s.groupKey.IsSome()
334+
}
335+
336+
// WhenId executes the given function if the ID field is specified.
337+
func (s *Specifier) WhenId(f func(ID)) {
338+
s.id.WhenSome(f)
339+
}
340+
341+
// WhenGroupPubKey executes the given function if asset group public key field
342+
// is specified.
343+
func (s *Specifier) WhenGroupPubKey(f func(btcec.PublicKey)) {
344+
s.groupKey.WhenSome(f)
345+
}
346+
347+
// UnwrapIdOrErr unwraps the ID field or returns an error if it is not
348+
// specified.
349+
func (s *Specifier) UnwrapIdOrErr() (ID, error) {
350+
id := s.id.UnwrapToPtr()
351+
if id == nil {
352+
return ID{}, ErrUnwrapAssetID
353+
}
354+
355+
return *id, nil
356+
}
357+
358+
// UnwrapIdToPtr unwraps the ID field to a pointer.
359+
func (s *Specifier) UnwrapIdToPtr() *ID {
360+
return s.id.UnwrapToPtr()
361+
}
362+
363+
// UnwrapGroupKeyToPtr unwraps the asset group public key field to a pointer.
364+
func (s *Specifier) UnwrapGroupKeyToPtr() *btcec.PublicKey {
365+
return s.groupKey.UnwrapToPtr()
366+
}
367+
250368
// Type denotes the asset types supported by the Taproot Asset protocol.
251369
type Type uint8
252370

@@ -1434,23 +1552,38 @@ func New(genesis Genesis, amount, locktime, relativeLocktime uint64,
14341552
}
14351553

14361554
// TapCommitmentKey is the key that maps to the root commitment for a specific
1437-
// asset group within a TapCommitment.
1555+
// asset within a TapCommitment.
14381556
//
14391557
// NOTE: This function is also used outside the asset package.
1440-
func TapCommitmentKey(assetID ID, groupKey *btcec.PublicKey) [32]byte {
1441-
if groupKey == nil {
1442-
return assetID
1558+
func TapCommitmentKey(assetSpecifier Specifier) [32]byte {
1559+
var commitmentKey [32]byte
1560+
1561+
switch {
1562+
case assetSpecifier.HasGroupPubKey():
1563+
assetSpecifier.WhenGroupPubKey(func(pubKey btcec.PublicKey) {
1564+
serializedPubKey := schnorr.SerializePubKey(&pubKey)
1565+
commitmentKey = sha256.Sum256(serializedPubKey)
1566+
})
1567+
1568+
case assetSpecifier.HasId():
1569+
assetSpecifier.WhenId(func(id ID) {
1570+
commitmentKey = id
1571+
})
1572+
1573+
default:
1574+
// We should never reach this point as the asset specifier
1575+
// should always have either a group public key, an asset ID, or
1576+
// both.
1577+
panic("invalid asset specifier")
14431578
}
1444-
return sha256.Sum256(schnorr.SerializePubKey(groupKey))
1579+
1580+
return commitmentKey
14451581
}
14461582

14471583
// TapCommitmentKey is the key that maps to the root commitment for a specific
14481584
// asset group within a TapCommitment.
14491585
func (a *Asset) TapCommitmentKey() [32]byte {
1450-
if a.GroupKey == nil {
1451-
return TapCommitmentKey(a.Genesis.ID(), nil)
1452-
}
1453-
return TapCommitmentKey(a.Genesis.ID(), &a.GroupKey.GroupPubKey)
1586+
return TapCommitmentKey(a.Specifier())
14541587
}
14551588

14561589
// AssetCommitmentKey returns a key which can be used to locate an
@@ -1893,6 +2026,12 @@ func (a *Asset) Leaf() (*mssmt.LeafNode, error) {
18932026
return mssmt.NewLeafNode(buf.Bytes(), a.Amount), nil
18942027
}
18952028

2029+
// Specifier returns the asset's specifier.
2030+
func (a *Asset) Specifier() Specifier {
2031+
id := a.Genesis.ID()
2032+
return NewSpecifierOptionalGroupKey(id, a.GroupKey)
2033+
}
2034+
18962035
// Validate ensures that an asset is valid.
18972036
func (a *Asset) Validate() error {
18982037
// TODO(ffranr): Add validation check for remaining fields.

commitment/asset.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"errors"
99
"fmt"
1010

11-
"github.com/btcsuite/btcd/btcec/v2"
1211
"github.com/lightninglabs/taproot-assets/asset"
1312
"github.com/lightninglabs/taproot-assets/fn"
1413
"github.com/lightninglabs/taproot-assets/mssmt"
@@ -151,16 +150,15 @@ func parseCommon(assets ...*asset.Asset) (*AssetCommitment, error) {
151150
assetsMap[key] = newAsset
152151
}
153152

154-
var groupPubKey *btcec.PublicKey
155-
if assetGroupKey != nil {
156-
groupPubKey = &assetGroupKey.GroupPubKey
157-
}
158-
159153
// The tapKey here is what will be used to place this asset commitment
160154
// into the top-level Taproot Asset commitment. For assets without a
161155
// group key, then this will be the normal asset ID. Otherwise, this'll
162156
// be the sha256 of the group key.
163-
tapKey := asset.TapCommitmentKey(firstAssetID, groupPubKey)
157+
assetSpecifier := asset.NewSpecifierOptionalGroupKey(
158+
firstAssetID, assetGroupKey,
159+
)
160+
161+
tapKey := asset.TapCommitmentKey(assetSpecifier)
164162

165163
return &AssetCommitment{
166164
Version: maxVersion,

rpcserver.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3234,11 +3234,14 @@ func (r *rpcServer) BurnAsset(ctx context.Context,
32343234
"burn_amount=%d)", assetID[:], serializedGroupKey,
32353235
in.AmountToBurn)
32363236

3237+
assetSpecifier := asset.NewSpecifierOptionalGroupPubKey(
3238+
assetID, groupKey,
3239+
)
3240+
32373241
fundResp, err := r.cfg.AssetWallet.FundBurn(
32383242
ctx, &tapsend.FundingDescriptor{
3239-
ID: assetID,
3240-
GroupKey: groupKey,
3241-
Amount: in.AmountToBurn,
3243+
AssetSpecifier: assetSpecifier,
3244+
Amount: in.AmountToBurn,
32423245
},
32433246
)
32443247
if err != nil {

tapdb/assets_store.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -806,14 +806,14 @@ func (a *AssetStore) constraintsToDbFilter(
806806
query.MinAnchorHeight,
807807
)
808808
}
809-
if query.AssetID != nil {
810-
assetID := query.AssetID[:]
811-
assetFilter.AssetIDFilter = assetID
812-
}
813-
if query.GroupKey != nil {
814-
groupKey := query.GroupKey.SerializeCompressed()
815-
assetFilter.KeyGroupFilter = groupKey
816-
}
809+
810+
// Add asset ID bytes and group key bytes to the filter. These
811+
// byte arrays are empty if the asset ID or group key is not
812+
// specified in the query.
813+
assetIDBytes, groupKeyBytes := query.AssetSpecifier.AsBytes()
814+
assetFilter.AssetIDFilter = assetIDBytes
815+
assetFilter.KeyGroupFilter = groupKeyBytes
816+
817817
// TODO(roasbeef): only want to allow asset ID or other and not
818818
// both?
819819

tapdb/assets_store_test.go

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,6 @@ func TestImportAssetProof(t *testing.T) {
280280

281281
// Add a random asset and corresponding proof into the database.
282282
testAsset, testProof := dbHandle.AddRandomAssetProof(t)
283-
assetID := testAsset.ID()
284283
initialBlob := testProof.Blob
285284

286285
// We should now be able to retrieve the set of all assets inserted on
@@ -314,11 +313,8 @@ func TestImportAssetProof(t *testing.T) {
314313
// We should also be able to fetch the created asset above based on
315314
// either the asset ID, or key group via the main coin selection
316315
// routine.
317-
var assetConstraints tapfreighter.CommitmentConstraints
318-
if testAsset.GroupKey != nil {
319-
assetConstraints.GroupKey = &testAsset.GroupKey.GroupPubKey
320-
} else {
321-
assetConstraints.AssetID = &assetID
316+
assetConstraints := tapfreighter.CommitmentConstraints{
317+
AssetSpecifier: testAsset.Specifier(),
322318
}
323319
selectedAssets, err := assetStore.ListEligibleCoins(
324320
ctxb, assetConstraints,
@@ -660,16 +656,20 @@ func (a *assetGenerator) genAssets(t *testing.T, assetStore *AssetStore,
660656
}
661657
}
662658

663-
func (a *assetGenerator) bindAssetID(i int, op wire.OutPoint) *asset.ID {
659+
func (a *assetGenerator) assetSpecifierAssetID(i int,
660+
op wire.OutPoint) asset.Specifier {
661+
664662
gen := a.assetGens[i]
665663
gen.FirstPrevOut = op
666664

667665
id := gen.ID()
668666

669-
return &id
667+
return asset.NewSpecifierFromId(id)
670668
}
671669

672-
func (a *assetGenerator) bindGroupKey(i int, op wire.OutPoint) *btcec.PublicKey {
670+
func (a *assetGenerator) assetSpecifierGroupKey(i int,
671+
op wire.OutPoint) asset.Specifier {
672+
673673
gen := a.assetGens[i]
674674
gen.FirstPrevOut = op
675675
genTweak := gen.ID()
@@ -678,8 +678,9 @@ func (a *assetGenerator) bindGroupKey(i int, op wire.OutPoint) *btcec.PublicKey
678678

679679
internalPriv := input.TweakPrivKey(&groupPriv, genTweak[:])
680680
tweakedPriv := txscript.TweakTaprootPrivKey(*internalPriv, nil)
681+
groupPubKey := tweakedPriv.PubKey()
681682

682-
return tweakedPriv.PubKey()
683+
return asset.NewSpecifierFromGroupKey(*groupPubKey)
683684
}
684685

685686
// TestFetchAllAssets tests that the different AssetQueryFilters work as
@@ -1001,7 +1002,7 @@ func TestSelectCommitment(t *testing.T) {
10011002
},
10021003
},
10031004
constraints: tapfreighter.CommitmentConstraints{
1004-
AssetID: assetGen.bindAssetID(
1005+
AssetSpecifier: assetGen.assetSpecifierAssetID(
10051006
0, assetGen.anchorPoints[0],
10061007
),
10071008
MinAmt: 2,
@@ -1023,7 +1024,7 @@ func TestSelectCommitment(t *testing.T) {
10231024
},
10241025
},
10251026
constraints: tapfreighter.CommitmentConstraints{
1026-
AssetID: assetGen.bindAssetID(
1027+
AssetSpecifier: assetGen.assetSpecifierAssetID(
10271028
0, assetGen.anchorPoints[0],
10281029
),
10291030
MinAmt: 10,
@@ -1044,7 +1045,7 @@ func TestSelectCommitment(t *testing.T) {
10441045
},
10451046
},
10461047
constraints: tapfreighter.CommitmentConstraints{
1047-
AssetID: assetGen.bindAssetID(
1048+
AssetSpecifier: assetGen.assetSpecifierAssetID(
10481049
1, assetGen.anchorPoints[1],
10491050
),
10501051
MinAmt: 10,
@@ -1075,7 +1076,7 @@ func TestSelectCommitment(t *testing.T) {
10751076
},
10761077
},
10771078
constraints: tapfreighter.CommitmentConstraints{
1078-
GroupKey: assetGen.bindGroupKey(
1079+
AssetSpecifier: assetGen.assetSpecifierGroupKey(
10791080
0, assetGen.anchorPoints[0],
10801081
),
10811082
MinAmt: 1,
@@ -1105,7 +1106,7 @@ func TestSelectCommitment(t *testing.T) {
11051106
},
11061107
},
11071108
constraints: tapfreighter.CommitmentConstraints{
1108-
AssetID: assetGen.bindAssetID(
1109+
AssetSpecifier: assetGen.assetSpecifierAssetID(
11091110
0, assetGen.anchorPoints[0],
11101111
),
11111112
MinAmt: 2,
@@ -1147,7 +1148,7 @@ func TestSelectCommitment(t *testing.T) {
11471148
},
11481149
},
11491150
constraints: tapfreighter.CommitmentConstraints{
1150-
GroupKey: assetGen.bindGroupKey(
1151+
AssetSpecifier: assetGen.assetSpecifierGroupKey(
11511152
0, assetGen.anchorPoints[0],
11521153
),
11531154
MinAmt: 1,

0 commit comments

Comments
 (0)