Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tapfreighter: don't send proofs for tombstones, remove tombstones and burns from commitments #556

Merged
merged 11 commits into from
Oct 10, 2023
9 changes: 7 additions & 2 deletions asset/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -1192,8 +1192,13 @@ func (a *Asset) Copy() *Asset {
if a.ScriptKey.TweakedScriptKey != nil {
assetCopy.ScriptKey.TweakedScriptKey = &TweakedScriptKey{}
assetCopy.ScriptKey.RawKey = a.ScriptKey.RawKey
assetCopy.ScriptKey.Tweak = make([]byte, len(a.ScriptKey.Tweak))
copy(assetCopy.ScriptKey.Tweak, a.ScriptKey.Tweak)

if len(a.ScriptKey.Tweak) > 0 {
assetCopy.ScriptKey.Tweak = make(
[]byte, len(a.ScriptKey.Tweak),
)
copy(assetCopy.ScriptKey.Tweak, a.ScriptKey.Tweak)
}
}

if a.GroupKey != nil {
Expand Down
4 changes: 2 additions & 2 deletions asset/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ func RandAssetWithValues(t testing.TB, genesis Genesis, groupKey *GroupKey,

t.Helper()

units := test.RandInt[uint64]() + 1
units := test.RandInt[uint32]() + 1

switch genesis.Type {
case Normal:
Expand All @@ -376,7 +376,7 @@ func RandAssetWithValues(t testing.TB, genesis Genesis, groupKey *GroupKey,
}

a, err := New(
genesis, units, 0, 0, scriptKey, groupKey,
genesis, uint64(units), 0, 0, scriptKey, groupKey,
WithAssetVersion(assetVersion),
)
require.NoError(t, err)
Expand Down
38 changes: 18 additions & 20 deletions commitment/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"errors"
"fmt"

"github.com/btcsuite/btcd/btcec/v2/schnorr"
"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 @@ -61,11 +61,11 @@ type AssetCommitment struct {
// Version is the max version of the assets committed.
Version asset.Version

// AssetID is the common identifier for all assets found within the
// TapKey is the common identifier for all assets found within the
// AssetCommitment. This can either be an asset.ID, which every
// committed asset must match, or the hash of an asset.GroupKey which
// every committed asset must match if their asset.ID differs.
AssetID [32]byte
TapKey [32]byte

// AssetType is the type of asset(s) committed to within the tree.
AssetType asset.Type
Expand Down Expand Up @@ -98,7 +98,7 @@ func parseCommon(assets ...*asset.Asset) (*AssetCommitment, error) {
assetType asset.Type
tapCommitmentKey [32]byte
maxVersion = asset.Version(0)
assetGenesis = assets[0].Genesis.ID()
firstAssetID = assets[0].Genesis.ID()
assetGroupKey = assets[0].GroupKey
assetsMap = make(CommittedAssets, len(assets))
)
Expand All @@ -125,7 +125,7 @@ func parseCommon(assets ...*asset.Asset) (*AssetCommitment, error) {
return nil, ErrAssetGroupKeyMismatch

case assetGroupKey == nil:
if assetGenesis != newAsset.Genesis.ID() {
if firstAssetID != newAsset.Genesis.ID() {
return nil, ErrAssetGenesisMismatch
}
}
Expand All @@ -151,22 +151,20 @@ func parseCommon(assets ...*asset.Asset) (*AssetCommitment, error) {
assetsMap[key] = newAsset
}

// The assetID here is what will be used to place this asset commitment
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.
var assetID [32]byte
if assetGroupKey == nil {
assetID = assetGenesis
} else {
assetID = sha256.Sum256(
schnorr.SerializePubKey(&assetGroupKey.GroupPubKey),
)
}
tapKey := asset.TapCommitmentKey(firstAssetID, groupPubKey)

return &AssetCommitment{
Version: maxVersion,
AssetID: assetID,
TapKey: tapKey,
AssetType: assetType,
assets: assetsMap,
}, nil
Expand Down Expand Up @@ -221,7 +219,7 @@ func (c *AssetCommitment) Upsert(newAsset *asset.Asset) error {
// The given Asset must have an ID that matches the AssetCommitment ID.
// The AssetCommitment ID is either a hash of the groupKey, or the ID
// of all the assets in the AssetCommitment.
if newAsset.TapCommitmentKey() != c.AssetID {
if newAsset.TapCommitmentKey() != c.TapKey {
if newAsset.GroupKey != nil {
return ErrAssetGroupKeyMismatch
}
Expand Down Expand Up @@ -269,7 +267,7 @@ func (c *AssetCommitment) Delete(oldAsset *asset.Asset) error {
// The given Asset must have an ID that matches the AssetCommitment ID.
// The AssetCommitment ID is either a hash of the groupKey, or the ID
// of all the assets in the AssetCommitment.
if oldAsset.TapCommitmentKey() != c.AssetID {
if oldAsset.TapCommitmentKey() != c.TapKey {
if oldAsset.GroupKey != nil {
return ErrAssetGroupKeyMismatch
}
Expand Down Expand Up @@ -322,7 +320,7 @@ func (c *AssetCommitment) Root() [sha256.Size]byte {
right := c.TreeRoot.Right.NodeHash()

h := sha256.New()
_, _ = h.Write(c.AssetID[:])
_, _ = h.Write(c.TapKey[:])
_, _ = h.Write(left[:])
_, _ = h.Write(right[:])
_ = binary.Write(h, binary.BigEndian, c.TreeRoot.NodeSum())
Expand All @@ -332,7 +330,7 @@ func (c *AssetCommitment) Root() [sha256.Size]byte {
// TapCommitmentKey computes the insertion key for this specific asset
// commitment to include in the Taproot Asset commitment MS-SMT.
func (c *AssetCommitment) TapCommitmentKey() [32]byte {
return c.AssetID
return c.TapKey
}

// TapCommitmentLeaf computes the leaf node for this specific asset commitment
Expand Down Expand Up @@ -390,7 +388,7 @@ func (c *AssetCommitment) Copy() (*AssetCommitment, error) {
treeRoot := c.TreeRoot.Copy().(*mssmt.BranchNode)
return &AssetCommitment{
Version: c.Version,
AssetID: c.AssetID,
TapKey: c.TapKey,
TreeRoot: treeRoot,
}, nil
}
Expand Down
45 changes: 43 additions & 2 deletions commitment/commitment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1082,6 +1082,12 @@ func TestUpdateTapCommitment(t *testing.T) {
groupKey1 := asset.RandGroupKey(t, genesis1, protoAsset1)
groupKey2 := asset.RandGroupKey(t, genesis2, protoAsset2)

// We also create a thirds asset which is in the same group as the first
// one, to ensure that we can properly create Taproot Asset commitments
// from asset commitments of the same group.
genesis3 := asset.RandGenesis(t, asset.Normal)
asset3 := randAsset(t, genesis3, groupKey1)

asset1 := protoAsset1.Copy()
asset1.GroupKey = groupKey1

Expand All @@ -1097,10 +1103,45 @@ func TestUpdateTapCommitment(t *testing.T) {
require.NoError(t, err)

commitmentKey2 := assetCommitment2.TapCommitmentKey()
assetCommitment3, err := NewAssetCommitment(asset3)
require.NoError(t, err)
commitmentKey3 := assetCommitment3.TapCommitmentKey()

// When creating a Taproot Asset commitment from all three assets, we
// expect two commitments to be created, one for each group.
cp1, err := assetCommitment1.Copy()
require.NoError(t, err)
cp2, err := assetCommitment2.Copy()
require.NoError(t, err)
cp3, err := assetCommitment3.Copy()
require.NoError(t, err)
commitment, err := NewTapCommitment(cp1, cp2, cp3)
require.NoError(t, err)
require.Len(t, commitment.Commitments(), 2)
require.Len(t, commitment.CommittedAssets(), 3)

require.Equal(t, commitmentKey1, commitmentKey3)

// Make sure we can still generate proper proofs for all assets.
p1, _, err := commitment.Proof(
commitmentKey1, asset1.AssetCommitmentKey(),
)
require.NoError(t, err)
require.True(t, p1.DeepEqual(asset1))
p2, _, err := commitment.Proof(
commitmentKey2, asset2.AssetCommitmentKey(),
)
require.NoError(t, err)
require.True(t, p2.DeepEqual(asset2))
p3, _, err := commitment.Proof(
commitmentKey3, asset3.AssetCommitmentKey(),
)
require.NoError(t, err)
require.True(t, p3.DeepEqual(asset3))

// Mint a new Taproot Asset commitment with only the first
// assetCommitment.
commitment, err := NewTapCommitment(assetCommitment1)
commitment, err = NewTapCommitment(assetCommitment1)
require.NoError(t, err)

copyOfCommitment, err := NewTapCommitment(assetCommitment1)
Expand Down Expand Up @@ -1201,7 +1242,7 @@ func TestAssetCommitmentDeepCopy(t *testing.T) {
require.NoError(t, err)

require.Equal(t, assetCommitment.Version, assetCommitmentCopy.Version)
require.Equal(t, assetCommitment.AssetID, assetCommitmentCopy.AssetID)
require.Equal(t, assetCommitment.TapKey, assetCommitmentCopy.TapKey)
require.True(
t, mssmt.IsEqualNode(
assetCommitment.TreeRoot, assetCommitmentCopy.TreeRoot,
Expand Down
4 changes: 2 additions & 2 deletions commitment/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ func AssetProofEncoder(w io.Writer, val any, buf *[8]byte) error {
if t, ok := val.(**AssetProof); ok {
records := []tlv.Record{
AssetProofVersionRecord(&(*t).Version),
AssetProofAssetIDRecord(&(*t).AssetID),
AssetProofAssetIDRecord(&(*t).TapKey),
AssetProofRecord(&(*t).Proof),
}
stream, err := tlv.NewStream(records...)
Expand All @@ -39,7 +39,7 @@ func AssetProofDecoder(r io.Reader, val any, buf *[8]byte, l uint64) error {
var proof AssetProof
records := []tlv.Record{
AssetProofVersionRecord(&proof.Version),
AssetProofAssetIDRecord(&proof.AssetID),
AssetProofAssetIDRecord(&proof.TapKey),
AssetProofRecord(&proof.Proof),
}
stream, err := tlv.NewStream(records...)
Expand Down
8 changes: 4 additions & 4 deletions commitment/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func NewTestFromProof(t testing.TB, p *Proof) *TestProof {
tp.AssetProof = &TestAssetProof{
Proof: mssmt.HexProof(t, &p.AssetProof.Proof),
Version: uint8(p.AssetProof.Version),
AssetID: hex.EncodeToString(p.AssetProof.AssetID[:]),
TapKey: hex.EncodeToString(p.AssetProof.TapKey[:]),
}
}

Expand Down Expand Up @@ -111,9 +111,9 @@ func (tp *TestProof) ToProof(t testing.TB) *Proof {
Proof: mssmt.ParseProof(t, tp.AssetProof.Proof),
Version: asset.Version(tp.AssetProof.Version),
}
assetID, err := hex.DecodeString(tp.AssetProof.AssetID)
assetID, err := hex.DecodeString(tp.AssetProof.TapKey)
require.NoError(t, err)
copy(p.AssetProof.AssetID[:], assetID)
copy(p.AssetProof.TapKey[:], assetID)
}

return p
Expand All @@ -122,7 +122,7 @@ func (tp *TestProof) ToProof(t testing.TB) *Proof {
type TestAssetProof struct {
Proof string `json:"proof"`
Version uint8 `json:"version"`
AssetID string `json:"asset_id"`
TapKey string `json:"tap_key"`
}

type TestTaprootAssetProof struct {
Expand Down
8 changes: 4 additions & 4 deletions commitment/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ type AssetProof struct {
// Version is the max version of the assets committed.
Version asset.Version

// AssetID is the common identifier for all assets found within the
// TapKey is the common identifier for all assets found within the
// AssetCommitment. This can either be an asset.ID, which every
// committed asset must match, otherwise an asset.GroupKey which every
// committed asset must match.
AssetID [32]byte
TapKey [32]byte
}

// TaprootAssetProof is the proof used along with an asset commitment leaf to
Expand Down Expand Up @@ -117,7 +117,7 @@ func (p Proof) DeriveByAssetInclusion(asset *asset.Asset) (*TapCommitment,
)
assetCommitment := &AssetCommitment{
Version: p.AssetProof.Version,
AssetID: p.AssetProof.AssetID,
TapKey: p.AssetProof.TapKey,
TreeRoot: assetProofRoot,
}

Expand Down Expand Up @@ -158,7 +158,7 @@ func (p Proof) DeriveByAssetExclusion(assetCommitmentKey [32]byte) (
)
assetCommitment := &AssetCommitment{
Version: p.AssetProof.Version,
AssetID: p.AssetProof.AssetID,
TapKey: p.AssetProof.TapKey,
TreeRoot: assetProofRoot,
}

Expand Down
54 changes: 30 additions & 24 deletions commitment/tap.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,26 +78,41 @@ type TapCommitment struct {

// NewTapCommitment creates a new Taproot Asset commitment for the given asset
// commitments capable of computing merkle proofs.
func NewTapCommitment(assets ...*AssetCommitment) (*TapCommitment, error) {
func NewTapCommitment(newCommitments ...*AssetCommitment) (*TapCommitment,
error) {

maxVersion := asset.V0
tree := mssmt.NewCompactedTree(mssmt.NewDefaultStore())
assetCommitments := make(AssetCommitments, len(assets))
for _, asset := range assets {
asset := asset
assetCommitments := make(AssetCommitments, len(newCommitments))
for idx := range newCommitments {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need to do this any longer, but does't hurt.

assetCommitment := newCommitments[idx]

if asset.Version > maxVersion {
maxVersion = asset.Version
if assetCommitment.Version > maxVersion {
maxVersion = assetCommitment.Version
}
key := asset.TapCommitmentKey()
leaf := asset.TapCommitmentLeaf()
key := assetCommitment.TapCommitmentKey()

// Do we already have an asset commitment for this key? If so,
// we need to merge them together.
existingCommitment, ok := assetCommitments[key]
if ok {
err := existingCommitment.Merge(assetCommitment)
if err != nil {
return nil, err
}

assetCommitment = existingCommitment
}

leaf := assetCommitment.TapCommitmentLeaf()

// TODO(bhandras): thread the context through.
_, err := tree.Insert(context.TODO(), key, leaf)
if err != nil {
return nil, err
}

assetCommitments[key] = asset
assetCommitments[key] = assetCommitment
}

root, err := tree.Root(context.Background())
Expand Down Expand Up @@ -365,7 +380,7 @@ func (c *TapCommitment) Proof(tapCommitmentKey,
proof.AssetProof = &AssetProof{
Proof: *assetProof,
Version: assetCommitment.Version,
AssetID: assetCommitment.AssetID,
TapKey: assetCommitment.TapKey,
}

return a, proof, nil
Expand Down Expand Up @@ -437,18 +452,14 @@ func (c *TapCommitment) Merge(other *TapCommitment) error {

// Otherwise, we'll need to merge the other asset commitments into
// this commitment.
for key, otherCommitment := range other.assetCommitments {
existingCommitment, ok := c.assetCommitments[key]
for key := range other.assetCommitments {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

key might not be the correct variable name after this change. I think it's an index now. Should we just use i instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, key is still correct IMO, since assetCommitments is a map, not a slice.

otherCommitment := other.assetCommitments[key]

// If we already have an asset commitment for this key, then we
// merge the two asset trees together.
existingCommitment, ok := c.assetCommitments[key]
if ok {
commitmentCopy, err := otherCommitment.Copy()
if err != nil {
return fmt.Errorf("error copying asset "+
"commitment: %w", err)
}
err = existingCommitment.Merge(commitmentCopy)
err := existingCommitment.Merge(otherCommitment)
if err != nil {
return fmt.Errorf("error merging asset "+
"commitment: %w", err)
Expand All @@ -459,12 +470,7 @@ func (c *TapCommitment) Merge(other *TapCommitment) error {

// With either the new or merged asset commitment obtained, we
// can now (re-)insert it into the Taproot Asset commitment.
existingCommitmentCopy, err := existingCommitment.Copy()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC we started to do this mainly as a precautionary mechanism.

if err != nil {
return fmt.Errorf("error copying asset commitment: "+
"%w", err)
}
if err := c.Upsert(existingCommitmentCopy); err != nil {
if err := c.Upsert(existingCommitment); err != nil {
return fmt.Errorf("error upserting other commitment: "+
"%w", err)
}
Expand Down
Loading