Skip to content

Commit

Permalink
Merge pull request #865 from lightninglabs/add-asset-specifier
Browse files Browse the repository at this point in the history
Add asset specifier
  • Loading branch information
Roasbeef authored Jul 19, 2024
2 parents 4a11fd1 + 4af6579 commit b154a99
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 88 deletions.
6 changes: 5 additions & 1 deletion address/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,11 @@ func (a *Tap) AttachGenesis(gen asset.Genesis) {
// TapCommitmentKey is the key that maps to the root commitment for the asset
// group specified by a Taproot Asset address.
func (a *Tap) TapCommitmentKey() [32]byte {
return asset.TapCommitmentKey(a.AssetID, a.GroupKey)
assetSpecifier := asset.NewSpecifierOptionalGroupPubKey(
a.AssetID, a.GroupKey,
)

return asset.TapCommitmentKey(assetSpecifier)
}

// AssetCommitmentKey is the key that maps to the asset leaf for the asset
Expand Down
161 changes: 150 additions & 11 deletions asset/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ const (
type EncodeType uint8

const (
// Encode normal is the normal encoding type for an asset.
// EncodeNormal normal is the normal encoding type for an asset.
EncodeNormal EncodeType = iota

// EncodeSegwit denotes that the witness vector field is not be be
// EncodeSegwit denotes that the witness vector field is not to be
// encoded.
EncodeSegwit
)
Expand Down Expand Up @@ -247,6 +247,124 @@ func DecodeGenesis(r io.Reader) (Genesis, error) {
return gen, err
}

var (
// ErrUnwrapAssetID is an error type which is returned when an asset ID
// cannot be unwrapped from a specifier.
ErrUnwrapAssetID = errors.New("unable to unwrap asset ID")
)

// Specifier is a type that can be used to specify an asset by its ID, its asset
// group public key, or both.
type Specifier struct {
// id is the asset ID.
id fn.Option[ID]

// groupKey is the asset group public key.
groupKey fn.Option[btcec.PublicKey]
}

// NewSpecifierOptionalGroupPubKey creates a new specifier that specifies an
// asset by its ID and an optional group public key.
func NewSpecifierOptionalGroupPubKey(id ID,
groupPubKey *btcec.PublicKey) Specifier {

s := Specifier{
id: fn.Some(id),
}

if groupPubKey != nil {
s.groupKey = fn.Some(*groupPubKey)
}

return s
}

// NewSpecifierOptionalGroupKey creates a new specifier that specifies an
// asset by its ID and an optional group key.
func NewSpecifierOptionalGroupKey(id ID, groupKey *GroupKey) Specifier {
s := Specifier{
id: fn.Some(id),
}

if groupKey != nil {
s.groupKey = fn.Some(groupKey.GroupPubKey)
}

return s
}

// NewSpecifierFromId creates a new specifier that specifies an asset by its ID.
func NewSpecifierFromId(id ID) Specifier {
return Specifier{
id: fn.Some(id),
}
}

// NewSpecifierFromGroupKey creates a new specifier that specifies an asset by
// its group public key.
func NewSpecifierFromGroupKey(groupPubKey btcec.PublicKey) Specifier {
return Specifier{
groupKey: fn.Some(groupPubKey),
}
}

// AsBytes returns the asset ID and group public key as byte slices.
func (s *Specifier) AsBytes() ([]byte, []byte) {
var assetIDBytes, groupKeyBytes []byte

s.WhenGroupPubKey(func(groupKey btcec.PublicKey) {
groupKeyBytes = groupKey.SerializeCompressed()
})

s.WhenId(func(id ID) {
assetIDBytes = id[:]
})

return assetIDBytes, groupKeyBytes
}

// HasId returns true if the asset ID field is specified.
func (s *Specifier) HasId() bool {
return s.id.IsSome()
}

// HasGroupPubKey returns true if the asset group public key field is specified.
func (s *Specifier) HasGroupPubKey() bool {
return s.groupKey.IsSome()
}

// WhenId executes the given function if the ID field is specified.
func (s *Specifier) WhenId(f func(ID)) {
s.id.WhenSome(f)
}

// WhenGroupPubKey executes the given function if asset group public key field
// is specified.
func (s *Specifier) WhenGroupPubKey(f func(btcec.PublicKey)) {
s.groupKey.WhenSome(f)
}

// UnwrapIdOrErr unwraps the ID field or returns an error if it is not
// specified.
func (s *Specifier) UnwrapIdOrErr() (ID, error) {
id := s.id.UnwrapToPtr()
if id == nil {
return ID{}, ErrUnwrapAssetID
}

return *id, nil
}

// UnwrapIdToPtr unwraps the ID field to a pointer.
func (s *Specifier) UnwrapIdToPtr() *ID {
return s.id.UnwrapToPtr()
}

// UnwrapGroupKeyToPtr unwraps the asset group public key field to a pointer.
func (s *Specifier) UnwrapGroupKeyToPtr() *btcec.PublicKey {
return s.groupKey.UnwrapToPtr()
}

// Type denotes the asset types supported by the Taproot Asset protocol.
type Type uint8

Expand Down Expand Up @@ -1434,23 +1552,38 @@ func New(genesis Genesis, amount, locktime, relativeLocktime uint64,
}

// TapCommitmentKey is the key that maps to the root commitment for a specific
// asset group within a TapCommitment.
// asset within a TapCommitment.
//
// NOTE: This function is also used outside the asset package.
func TapCommitmentKey(assetID ID, groupKey *btcec.PublicKey) [32]byte {
if groupKey == nil {
return assetID
func TapCommitmentKey(assetSpecifier Specifier) [32]byte {
var commitmentKey [32]byte

switch {
case assetSpecifier.HasGroupPubKey():
assetSpecifier.WhenGroupPubKey(func(pubKey btcec.PublicKey) {
serializedPubKey := schnorr.SerializePubKey(&pubKey)
commitmentKey = sha256.Sum256(serializedPubKey)
})

case assetSpecifier.HasId():
assetSpecifier.WhenId(func(id ID) {
commitmentKey = id
})

default:
// We should never reach this point as the asset specifier
// should always have either a group public key, an asset ID, or
// both.
panic("invalid asset specifier")
}
return sha256.Sum256(schnorr.SerializePubKey(groupKey))

return commitmentKey
}

// TapCommitmentKey is the key that maps to the root commitment for a specific
// asset group within a TapCommitment.
func (a *Asset) TapCommitmentKey() [32]byte {
if a.GroupKey == nil {
return TapCommitmentKey(a.Genesis.ID(), nil)
}
return TapCommitmentKey(a.Genesis.ID(), &a.GroupKey.GroupPubKey)
return TapCommitmentKey(a.Specifier())
}

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

// Specifier returns the asset's specifier.
func (a *Asset) Specifier() Specifier {
id := a.Genesis.ID()
return NewSpecifierOptionalGroupKey(id, a.GroupKey)
}

// Validate ensures that an asset is valid.
func (a *Asset) Validate() error {
// TODO(ffranr): Add validation check for remaining fields.
Expand Down
12 changes: 5 additions & 7 deletions commitment/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"errors"
"fmt"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightninglabs/taproot-assets/asset"
"github.com/lightninglabs/taproot-assets/fn"
"github.com/lightninglabs/taproot-assets/mssmt"
Expand Down Expand Up @@ -151,16 +150,15 @@ func parseCommon(assets ...*asset.Asset) (*AssetCommitment, error) {
assetsMap[key] = newAsset
}

var groupPubKey *btcec.PublicKey
if assetGroupKey != nil {
groupPubKey = &assetGroupKey.GroupPubKey
}

// The tapKey here is what will be used to place this asset commitment
// into the top-level Taproot Asset commitment. For assets without a
// group key, then this will be the normal asset ID. Otherwise, this'll
// be the sha256 of the group key.
tapKey := asset.TapCommitmentKey(firstAssetID, groupPubKey)
assetSpecifier := asset.NewSpecifierOptionalGroupKey(
firstAssetID, assetGroupKey,
)

tapKey := asset.TapCommitmentKey(assetSpecifier)

return &AssetCommitment{
Version: maxVersion,
Expand Down
9 changes: 6 additions & 3 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -3234,11 +3234,14 @@ func (r *rpcServer) BurnAsset(ctx context.Context,
"burn_amount=%d)", assetID[:], serializedGroupKey,
in.AmountToBurn)

assetSpecifier := asset.NewSpecifierOptionalGroupPubKey(
assetID, groupKey,
)

fundResp, err := r.cfg.AssetWallet.FundBurn(
ctx, &tapsend.FundingDescriptor{
ID: assetID,
GroupKey: groupKey,
Amount: in.AmountToBurn,
AssetSpecifier: assetSpecifier,
Amount: in.AmountToBurn,
},
)
if err != nil {
Expand Down
16 changes: 8 additions & 8 deletions tapdb/assets_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -806,14 +806,14 @@ func (a *AssetStore) constraintsToDbFilter(
query.MinAnchorHeight,
)
}
if query.AssetID != nil {
assetID := query.AssetID[:]
assetFilter.AssetIDFilter = assetID
}
if query.GroupKey != nil {
groupKey := query.GroupKey.SerializeCompressed()
assetFilter.KeyGroupFilter = groupKey
}

// Add asset ID bytes and group key bytes to the filter. These
// byte arrays are empty if the asset ID or group key is not
// specified in the query.
assetIDBytes, groupKeyBytes := query.AssetSpecifier.AsBytes()
assetFilter.AssetIDFilter = assetIDBytes
assetFilter.KeyGroupFilter = groupKeyBytes

// TODO(roasbeef): only want to allow asset ID or other and not
// both?

Expand Down
33 changes: 17 additions & 16 deletions tapdb/assets_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,6 @@ func TestImportAssetProof(t *testing.T) {

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

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

func (a *assetGenerator) bindAssetID(i int, op wire.OutPoint) *asset.ID {
func (a *assetGenerator) assetSpecifierAssetID(i int,
op wire.OutPoint) asset.Specifier {

gen := a.assetGens[i]
gen.FirstPrevOut = op

id := gen.ID()

return &id
return asset.NewSpecifierFromId(id)
}

func (a *assetGenerator) bindGroupKey(i int, op wire.OutPoint) *btcec.PublicKey {
func (a *assetGenerator) assetSpecifierGroupKey(i int,
op wire.OutPoint) asset.Specifier {

gen := a.assetGens[i]
gen.FirstPrevOut = op
genTweak := gen.ID()
Expand All @@ -678,8 +678,9 @@ func (a *assetGenerator) bindGroupKey(i int, op wire.OutPoint) *btcec.PublicKey

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

return tweakedPriv.PubKey()
return asset.NewSpecifierFromGroupKey(*groupPubKey)
}

// TestFetchAllAssets tests that the different AssetQueryFilters work as
Expand Down Expand Up @@ -1001,7 +1002,7 @@ func TestSelectCommitment(t *testing.T) {
},
},
constraints: tapfreighter.CommitmentConstraints{
AssetID: assetGen.bindAssetID(
AssetSpecifier: assetGen.assetSpecifierAssetID(
0, assetGen.anchorPoints[0],
),
MinAmt: 2,
Expand All @@ -1023,7 +1024,7 @@ func TestSelectCommitment(t *testing.T) {
},
},
constraints: tapfreighter.CommitmentConstraints{
AssetID: assetGen.bindAssetID(
AssetSpecifier: assetGen.assetSpecifierAssetID(
0, assetGen.anchorPoints[0],
),
MinAmt: 10,
Expand All @@ -1044,7 +1045,7 @@ func TestSelectCommitment(t *testing.T) {
},
},
constraints: tapfreighter.CommitmentConstraints{
AssetID: assetGen.bindAssetID(
AssetSpecifier: assetGen.assetSpecifierAssetID(
1, assetGen.anchorPoints[1],
),
MinAmt: 10,
Expand Down Expand Up @@ -1075,7 +1076,7 @@ func TestSelectCommitment(t *testing.T) {
},
},
constraints: tapfreighter.CommitmentConstraints{
GroupKey: assetGen.bindGroupKey(
AssetSpecifier: assetGen.assetSpecifierGroupKey(
0, assetGen.anchorPoints[0],
),
MinAmt: 1,
Expand Down Expand Up @@ -1105,7 +1106,7 @@ func TestSelectCommitment(t *testing.T) {
},
},
constraints: tapfreighter.CommitmentConstraints{
AssetID: assetGen.bindAssetID(
AssetSpecifier: assetGen.assetSpecifierAssetID(
0, assetGen.anchorPoints[0],
),
MinAmt: 2,
Expand Down Expand Up @@ -1147,7 +1148,7 @@ func TestSelectCommitment(t *testing.T) {
},
},
constraints: tapfreighter.CommitmentConstraints{
GroupKey: assetGen.bindGroupKey(
AssetSpecifier: assetGen.assetSpecifierGroupKey(
0, assetGen.anchorPoints[0],
),
MinAmt: 1,
Expand Down
Loading

0 comments on commit b154a99

Please sign in to comment.