From a332fe737b89141beef1d7c3fd87fba3ed9200b4 Mon Sep 17 00:00:00 2001 From: ffranr Date: Wed, 4 Dec 2024 14:48:12 +0000 Subject: [PATCH 1/3] asset+multi: refactor GroupKeyReveal into an interface Rename the existing `GroupKeyReveal` struct to `GroupKeyRevealV0` and introduce a new `GroupKeyReveal` interface. This change enables adding new versions of GroupKeyReveal while retaining the V0 type for proper documentation of its behavior. Support for GroupKeyReveal V0 will be preserved. --- asset/asset.go | 71 ++++++++++++++++++++++++++++++++++++------ asset/mock.go | 14 ++++----- proof/encoding.go | 22 +++++++------ proof/mint.go | 11 +++---- proof/mock.go | 10 +++--- proof/proof.go | 12 ++++--- proof/proof_test.go | 24 +++++++------- proof/records.go | 4 +-- rpcserver.go | 5 +-- tapdb/universe.go | 4 +-- tapdb/universe_test.go | 6 ++-- 11 files changed, 119 insertions(+), 64 deletions(-) diff --git a/asset/asset.go b/asset/asset.go index ad5ba6839..dd847b992 100644 --- a/asset/asset.go +++ b/asset/asset.go @@ -918,22 +918,75 @@ type GroupVirtualTx struct { TweakedKey btcec.PublicKey } -// GroupKeyReveal is a type for representing the data used to derive the tweaked -// key used to identify an asset group. The final tweaked key is the result of: -// TapTweak(groupInternalKey, tapscriptRoot) -type GroupKeyReveal struct { +// GroupKeyReveal represents the data used to derive the adjusted key that +// uniquely identifies an asset group. +type GroupKeyReveal interface { + // RawKey returns the raw key of the group key reveal. + RawKey() SerializedKey + + // SetRawKey sets the raw key of the group key reveal. + SetRawKey(SerializedKey) + + // TapscriptRoot returns the tapscript root of the group key reveal. + TapscriptRoot() []byte + + // SetTapscriptRoot sets the tapscript root of the group key reveal. + SetTapscriptRoot([]byte) + + // GroupPubKey returns the group public key derived from the group key + // reveal. + GroupPubKey(assetID ID) (*btcec.PublicKey, error) +} + +// GroupKeyRevealV0 is a version 0 group key reveal type for representing the +// data used to derive the tweaked key used to identify an asset group. The +// final tweaked key is the result of: TapTweak(groupInternalKey, tapscriptRoot) +type GroupKeyRevealV0 struct { // RawKey is the public key that is tweaked twice to derive the final // tweaked group key. The final tweaked key is the result of: // internalKey = rawKey + singleTweak * G // tweakedGroupKey = TapTweak(internalKey, tapTweak) - RawKey SerializedKey + rawKey SerializedKey // TapscriptRoot is the root of the Tapscript tree that commits to all // script spend conditions for the group key. Instead of spending an // asset, these scripts are used to define witnesses more complex than // a Schnorr signature for reissuing assets. This is either empty/nil or // a 32-byte hash. - TapscriptRoot []byte + tapscriptRoot []byte +} + +// Ensure that GroupKeyRevealV0 implements the GroupKeyReveal interface. +var _ GroupKeyReveal = (*GroupKeyRevealV0)(nil) + +// NewGroupKeyRevealV0 creates a new version 0 group key reveal instance. +func NewGroupKeyRevealV0(rawKey SerializedKey, + tapscriptRoot []byte) GroupKeyReveal { + + return &GroupKeyRevealV0{ + rawKey: rawKey, + tapscriptRoot: tapscriptRoot, + } +} + +// RawKey returns the raw key of the group key reveal. +func (g *GroupKeyRevealV0) RawKey() SerializedKey { + return g.rawKey +} + +// SetRawKey sets the raw key of the group key reveal. +func (g *GroupKeyRevealV0) SetRawKey(rawKey SerializedKey) { + g.rawKey = rawKey +} + +// TapscriptRoot returns the tapscript root of the group key reveal. +func (g *GroupKeyRevealV0) TapscriptRoot() []byte { + return g.tapscriptRoot +} + +// SetTapscriptRoot sets the tapscript root of the group key reveal. +func (g *GroupKeyRevealV0) SetTapscriptRoot(tapscriptRoot []byte) { + g.tapscriptRoot = tapscriptRoot } // PendingGroupWitness specifies the asset group witness for an asset seedling @@ -944,13 +997,13 @@ type PendingGroupWitness struct { } // GroupPubKey returns the group public key derived from the group key reveal. -func (g *GroupKeyReveal) GroupPubKey(assetID ID) (*btcec.PublicKey, error) { - rawKey, err := g.RawKey.ToPubKey() +func (g *GroupKeyRevealV0) GroupPubKey(assetID ID) (*btcec.PublicKey, error) { + rawKey, err := g.RawKey().ToPubKey() if err != nil { return nil, fmt.Errorf("group reveal raw key invalid: %w", err) } - return GroupPubKey(rawKey, assetID[:], g.TapscriptRoot) + return GroupPubKey(rawKey, assetID[:], g.TapscriptRoot()) } // GroupPubKey derives a tweaked group key from a public key and two tweaks; diff --git a/asset/mock.go b/asset/mock.go index 7fc688aed..3b15651b6 100644 --- a/asset/mock.go +++ b/asset/mock.go @@ -971,13 +971,14 @@ func (tgr *TestGenesisReveal) ToGenesisReveal(t testing.TB) *Genesis { } func NewTestFromGroupKeyReveal(t testing.TB, - gkr *GroupKeyReveal) *TestGroupKeyReveal { + gkr GroupKeyReveal) *TestGroupKeyReveal { t.Helper() + rawKey := gkr.RawKey() return &TestGroupKeyReveal{ - RawKey: hex.EncodeToString(gkr.RawKey[:]), - TapscriptRoot: hex.EncodeToString(gkr.TapscriptRoot), + RawKey: hex.EncodeToString(rawKey[:]), + TapscriptRoot: hex.EncodeToString(gkr.TapscriptRoot()), } } @@ -986,15 +987,12 @@ type TestGroupKeyReveal struct { TapscriptRoot string `json:"tapscript_root"` } -func (tgkr *TestGroupKeyReveal) ToGroupKeyReveal(t testing.TB) *GroupKeyReveal { +func (tgkr *TestGroupKeyReveal) ToGroupKeyReveal(t testing.TB) GroupKeyReveal { t.Helper() rawKey := test.ParsePubKey(t, tgkr.RawKey) tapscriptRoot, err := hex.DecodeString(tgkr.TapscriptRoot) require.NoError(t, err) - return &GroupKeyReveal{ - RawKey: ToSerialized(rawKey), - TapscriptRoot: tapscriptRoot, - } + return NewGroupKeyRevealV0(ToSerialized(rawKey), tapscriptRoot) } diff --git a/proof/encoding.go b/proof/encoding.go index 2630dd5ea..342e3a6a3 100644 --- a/proof/encoding.go +++ b/proof/encoding.go @@ -467,13 +467,13 @@ func GenesisRevealDecoder(r io.Reader, val any, buf *[8]byte, l uint64) error { } func GroupKeyRevealEncoder(w io.Writer, val any, buf *[8]byte) error { - if t, ok := val.(**asset.GroupKeyReveal); ok { - key := &(*t).RawKey - if err := asset.SerializedKeyEncoder(w, key, buf); err != nil { + if t, ok := val.(*asset.GroupKeyReveal); ok { + key := (*t).RawKey() + if err := asset.SerializedKeyEncoder(w, &key, buf); err != nil { return err } - root := &(*t).TapscriptRoot - return tlv.EVarBytes(w, root, buf) + root := (*t).TapscriptRoot() + return tlv.EVarBytes(w, &root, buf) } return tlv.NewTypeForEncodingErr(val, "GroupKeyReveal") @@ -489,20 +489,22 @@ func GroupKeyRevealDecoder(r io.Reader, val any, buf *[8]byte, l uint64) error { ErrProofInvalid) } - if typ, ok := val.(**asset.GroupKeyReveal); ok { - var reveal asset.GroupKeyReveal + if typ, ok := val.(*asset.GroupKeyReveal); ok { + var rawKey asset.SerializedKey err := asset.SerializedKeyDecoder( - r, &reveal.RawKey, buf, btcec.PubKeyBytesLenCompressed, + r, &rawKey, buf, btcec.PubKeyBytesLenCompressed, ) if err != nil { return err } remaining := l - btcec.PubKeyBytesLenCompressed - err = tlv.DVarBytes(r, &reveal.TapscriptRoot, buf, remaining) + var tapscriptRoot []byte + err = tlv.DVarBytes(r, &tapscriptRoot, buf, remaining) if err != nil { return err } - *typ = &reveal + + *typ = asset.NewGroupKeyRevealV0(rawKey, tapscriptRoot) return nil } return tlv.NewTypeForEncodingErr(val, "GroupKeyReveal") diff --git a/proof/mint.go b/proof/mint.go index 3d1db2aef..e77d3b9c5 100644 --- a/proof/mint.go +++ b/proof/mint.go @@ -388,12 +388,11 @@ func committedProofs(baseProof *Proof, tapTreeRoot *commitment.TapCommitment, err := groupAnchorVerifier(&newAsset.Genesis, groupKey) if err == nil { - groupReveal := &asset.GroupKeyReveal{ - RawKey: asset.ToSerialized( - groupKey.RawKey.PubKey, - ), - TapscriptRoot: groupKey.TapscriptRoot, - } + rawKey := asset.ToSerialized( + groupKey.RawKey.PubKey, + ) + groupReveal := asset.NewGroupKeyRevealV0( + rawKey, groupKey.TapscriptRoot) assetProof.GroupKeyReveal = groupReveal } } diff --git a/proof/mock.go b/proof/mock.go index 41fbb5893..71bea49a4 100644 --- a/proof/mock.go +++ b/proof/mock.go @@ -39,10 +39,10 @@ func RandProof(t testing.TB, genesis asset.Genesis, t, genesis, 1, 0, 0, tweakedScriptKey, nil, ) groupKey := asset.RandGroupKey(t, genesis, protoAsset) - groupReveal := asset.GroupKeyReveal{ - RawKey: asset.ToSerialized(&groupKey.GroupPubKey), - TapscriptRoot: test.RandBytes(32), - } + groupReveal := asset.NewGroupKeyRevealV0( + asset.ToSerialized(&groupKey.GroupPubKey), + test.RandBytes(32), + ) amount := uint64(1) mintCommitment, assets, err := commitment.Mint( @@ -146,7 +146,7 @@ func RandProof(t testing.TB, genesis asset.Genesis, }, ChallengeWitness: wire.TxWitness{[]byte("foo"), []byte("bar")}, GenesisReveal: &genesis, - GroupKeyReveal: &groupReveal, + GroupKeyReveal: groupReveal, } } diff --git a/proof/proof.go b/proof/proof.go index f6705d2fe..d893daebf 100644 --- a/proof/proof.go +++ b/proof/proof.go @@ -301,11 +301,13 @@ type Proof struct { // re-derivation of the asset group key. GenesisReveal *asset.Genesis - // GroupKeyReveal is an optional set of bytes that represent the public - // key and Tapscript root used to derive the final tweaked group key for - // the asset group. This field must be provided for issuance proofs of - // grouped assets. - GroupKeyReveal *asset.GroupKeyReveal + // GroupKeyReveal contains the data required to derive the final tweaked + // group key for an asset group. + // + // NOTE: This field is mandatory for the group anchor (i.e., the initial + // minting tranche of an asset group). Subsequent minting tranches + // require only a valid signature for the previously revealed group key. + GroupKeyReveal asset.GroupKeyReveal // UnknownOddTypes is a map of unknown odd types that were encountered // during decoding. This map is used to preserve unknown types that we diff --git a/proof/proof_test.go b/proof/proof_test.go index 09d3f9e2a..d1ce5c2c8 100644 --- a/proof/proof_test.go +++ b/proof/proof_test.go @@ -186,7 +186,7 @@ func TestProofEncoding(t *testing.T) { require.False(t, IsSingleProof(nil)) // Test with a nil tapscript root in the group reveal. - proof.GroupKeyReveal.TapscriptRoot = nil + proof.GroupKeyReveal.SetTapscriptRoot(nil) file, err = NewFile(V0, proof, proof) require.NoError(t, err) proof.AdditionalInputs = []File{*file, *file} @@ -262,12 +262,10 @@ func genRandomGenesisWithProof(t testing.TB, assetType asset.Type, asset.WithAssetVersion(assetVersion), ) assetGroupKey := asset.RandGroupKey(t, assetGenesis, protoAsset) - groupKeyReveal := &asset.GroupKeyReveal{ - RawKey: asset.ToSerialized( - assetGroupKey.RawKey.PubKey, - ), - TapscriptRoot: assetGroupKey.TapscriptRoot, - } + groupKeyReveal := asset.NewGroupKeyRevealV0( + asset.ToSerialized(assetGroupKey.RawKey.PubKey), + assetGroupKey.TapscriptRoot, + ) if groupRevealMutator != nil { groupRevealMutator(groupKeyReveal) @@ -362,7 +360,7 @@ func genRandomGenesisWithProof(t testing.TB, assetType asset.Type, type genMutator func(*asset.Genesis) -type groupRevealMutator func(*asset.GroupKeyReveal) +type groupRevealMutator func(asset.GroupKeyReveal) type genRevealMutator func(*asset.Genesis) *asset.Genesis @@ -557,8 +555,10 @@ func TestGenesisProofVerification(t *testing.T) { name: "group key reveal invalid key", assetType: asset.Collectible, noMetaHash: true, - groupRevealMutator: func(gkr *asset.GroupKeyReveal) { - gkr.RawKey[0] = 0x01 + groupRevealMutator: func(gkr asset.GroupKeyReveal) { + rawKey := gkr.RawKey() + rawKey[0] = 0x01 + gkr.SetRawKey(rawKey) }, expectedErr: secp256k1.ErrPubKeyInvalidFormat, }, @@ -567,8 +567,8 @@ func TestGenesisProofVerification(t *testing.T) { assetType: asset.Normal, amount: &amount, noMetaHash: true, - groupRevealMutator: func(gkr *asset.GroupKeyReveal) { - gkr.TapscriptRoot = test.RandBytes(32) + groupRevealMutator: func(gkr asset.GroupKeyReveal) { + gkr.SetTapscriptRoot(test.RandBytes(32)) }, expectedErr: ErrGroupKeyRevealMismatch, }, diff --git a/proof/records.go b/proof/records.go index 4cccfd620..1bd5e7584 100644 --- a/proof/records.go +++ b/proof/records.go @@ -378,14 +378,14 @@ func GenesisRevealRecord(genesis **asset.Genesis) tlv.Record { ) } -func GroupKeyRevealRecord(reveal **asset.GroupKeyReveal) tlv.Record { +func GroupKeyRevealRecord(reveal *asset.GroupKeyReveal) tlv.Record { recordSize := func() uint64 { if reveal == nil || *reveal == nil { return 0 } r := *reveal return uint64( - btcec.PubKeyBytesLenCompressed + len(r.TapscriptRoot), + btcec.PubKeyBytesLenCompressed + len(r.TapscriptRoot()), ) } return tlv.MakeDynamicRecord( diff --git a/rpcserver.go b/rpcserver.go index e8dfd27e8..cf78d4e36 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1817,9 +1817,10 @@ func (r *rpcServer) marshalProof(ctx context.Context, p *proof.Proof, var GroupKeyReveal taprpc.GroupKeyReveal if rpcGroupKey != nil { + rawKey := rpcGroupKey.RawKey() GroupKeyReveal = taprpc.GroupKeyReveal{ - RawGroupKey: rpcGroupKey.RawKey[:], - TapscriptRoot: rpcGroupKey.TapscriptRoot, + RawGroupKey: rawKey[:], + TapscriptRoot: rpcGroupKey.TapscriptRoot(), } } diff --git a/tapdb/universe.go b/tapdb/universe.go index ee263125a..5725f0cd9 100644 --- a/tapdb/universe.go +++ b/tapdb/universe.go @@ -273,7 +273,7 @@ func upsertAssetGen(ctx context.Context, db UpsertAssetStore, // key. if genesisProof.GroupKeyReveal != nil { reveal := genesisProof.GroupKeyReveal - rawKey, err := reveal.RawKey.ToPubKey() + rawKey, err := reveal.RawKey().ToPubKey() if err != nil { return 0, err } @@ -281,7 +281,7 @@ func upsertAssetGen(ctx context.Context, db UpsertAssetStore, fullGroupKey.RawKey = keychain.KeyDescriptor{ PubKey: rawKey, } - fullGroupKey.TapscriptRoot = reveal.TapscriptRoot + fullGroupKey.TapscriptRoot = reveal.TapscriptRoot() } _, err = upsertGroupKey( ctx, fullGroupKey, db, genPointID, genAssetID, diff --git a/tapdb/universe_test.go b/tapdb/universe_test.go index 11f13a958..65e3a4d6b 100644 --- a/tapdb/universe_test.go +++ b/tapdb/universe_test.go @@ -212,9 +212,9 @@ func randMintingLeaf(t *testing.T, assetGen asset.Genesis, leaf.GroupKey = assetGroupKey randProof.Asset.GroupKey = assetGroupKey - randProof.GroupKeyReveal = &asset.GroupKeyReveal{ - RawKey: asset.ToSerialized(groupKey), - } + randProof.GroupKeyReveal = asset.NewGroupKeyRevealV0( + asset.ToSerialized(groupKey), nil, + ) } leaf.Asset = &randProof.Asset From 23735cc1a1a7748bfb7c00bf9cf8ad109afe277b Mon Sep 17 00:00:00 2001 From: ffranr Date: Wed, 4 Dec 2024 14:50:17 +0000 Subject: [PATCH 2/3] asset: move PendingGroupWitness Relocate the PendingGroupWitness struct to improve code organization and readability. --- asset/asset.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/asset/asset.go b/asset/asset.go index dd847b992..25bd33d3f 100644 --- a/asset/asset.go +++ b/asset/asset.go @@ -989,13 +989,6 @@ func (g *GroupKeyRevealV0) SetTapscriptRoot(tapscriptRoot []byte) { g.tapscriptRoot = tapscriptRoot } -// PendingGroupWitness specifies the asset group witness for an asset seedling -// in an unsealed minting batch. -type PendingGroupWitness struct { - GenID ID - Witness wire.TxWitness -} - // GroupPubKey returns the group public key derived from the group key reveal. func (g *GroupKeyRevealV0) GroupPubKey(assetID ID) (*btcec.PublicKey, error) { rawKey, err := g.RawKey().ToPubKey() @@ -1544,6 +1537,13 @@ func DeriveGroupKey(genSigner GenesisSigner, genTx GroupVirtualTx, }, nil } +// PendingGroupWitness specifies the asset group witness for an asset seedling +// in an unsealed minting batch. +type PendingGroupWitness struct { + GenID ID + Witness wire.TxWitness +} + // Asset represents a Taproot asset. type Asset struct { // Version is the Taproot Asset version of the asset. From 2fa1fb59b9bb9e40106120e3250ca00da2892f45 Mon Sep 17 00:00:00 2001 From: ffranr Date: Wed, 4 Dec 2024 14:58:22 +0000 Subject: [PATCH 3/3] multi: rename asset.GroupPubKey to GroupPubKeyV0 Renamed the function to `GroupPubKeyV0` to allow for the introduction of future versions while maintaining clarity and compatibility. --- asset/asset.go | 15 ++++++++------- asset/asset_test.go | 4 ++-- itest/assertions.go | 2 +- tapgarden/caretaker.go | 2 +- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/asset/asset.go b/asset/asset.go index 25bd33d3f..661267f2c 100644 --- a/asset/asset.go +++ b/asset/asset.go @@ -996,17 +996,18 @@ func (g *GroupKeyRevealV0) GroupPubKey(assetID ID) (*btcec.PublicKey, error) { return nil, fmt.Errorf("group reveal raw key invalid: %w", err) } - return GroupPubKey(rawKey, assetID[:], g.TapscriptRoot()) + return GroupPubKeyV0(rawKey, assetID[:], g.TapscriptRoot()) } -// GroupPubKey derives a tweaked group key from a public key and two tweaks; -// the single tweak is the asset ID of the group anchor asset, and the tapTweak -// is the root of a tapscript tree that commits to script-based conditions for -// reissuing assets as part of this asset group. The tweaked key is defined by: +// GroupPubKeyV0 derives a version 0 tweaked group key from a public key and two +// tweaks; the single tweak is the asset ID of the group anchor asset, and the +// tapTweak is the root of a tapscript tree that commits to script-based +// conditions for reissuing assets as part of this asset group. The tweaked key +// is defined by: // // internalKey = rawKey + singleTweak * G // tweakedGroupKey = TapTweak(internalKey, tapTweak) -func GroupPubKey(rawKey *btcec.PublicKey, singleTweak, tapTweak []byte) ( +func GroupPubKeyV0(rawKey *btcec.PublicKey, singleTweak, tapTweak []byte) ( *btcec.PublicKey, error) { if len(singleTweak) != sha256.Size { @@ -1417,7 +1418,7 @@ func (req *GroupKeyRequest) BuildGroupVirtualTx(genBuilder GenesisTxBuilder) ( // Compute the tweaked group key and set it in the asset before // creating the virtual minting transaction. genesisTweak := req.AnchorGen.ID() - tweakedGroupKey, err := GroupPubKey( + tweakedGroupKey, err := GroupPubKeyV0( req.RawKey.PubKey, genesisTweak[:], req.TapscriptRoot, ) if err != nil { diff --git a/asset/asset_test.go b/asset/asset_test.go index 449e08be8..44f37fc69 100644 --- a/asset/asset_test.go +++ b/asset/asset_test.go @@ -920,10 +920,10 @@ func TestAssetGroupKey(t *testing.T) { // Group key tweaking should fail when given invalid tweaks. badTweak := test.RandBytes(33) - _, err = GroupPubKey(groupPub, badTweak, badTweak) + _, err = GroupPubKeyV0(groupPub, badTweak, badTweak) require.Error(t, err) - _, err = GroupPubKey(groupPub, groupTweak[:], badTweak) + _, err = GroupPubKeyV0(groupPub, groupTweak[:], badTweak) require.Error(t, err) } diff --git a/itest/assertions.go b/itest/assertions.go index 2c0abdd40..33a640dd1 100644 --- a/itest/assertions.go +++ b/itest/assertions.go @@ -1403,7 +1403,7 @@ func AssertGroupAnchor(t *testing.T, anchorGen *asset.Genesis, // TODO(jhb): add tapscript root support anchorTweak := anchorGen.ID() - computedGroupPubKey, err := asset.GroupPubKey( + computedGroupPubKey, err := asset.GroupPubKeyV0( internalPubKey, anchorTweak[:], nil, ) require.NoError(t, err) diff --git a/tapgarden/caretaker.go b/tapgarden/caretaker.go index a3434f3a1..6968251f0 100644 --- a/tapgarden/caretaker.go +++ b/tapgarden/caretaker.go @@ -1562,7 +1562,7 @@ func GenRawGroupAnchorVerifier(ctx context.Context) func(*asset.Genesis, groupAnchor, err := groupAnchors.Get(assetGroupKey) if err != nil { singleTweak := gen.ID() - tweakedGroupKey, err := asset.GroupPubKey( + tweakedGroupKey, err := asset.GroupPubKeyV0( groupKey.RawKey.PubKey, singleTweak[:], groupKey.TapscriptRoot, )