diff --git a/itest/mint_fund_seal_test.go b/itest/mint_fund_seal_test.go index 4e1b8c4fd5..e853e08590 100644 --- a/itest/mint_fund_seal_test.go +++ b/itest/mint_fund_seal_test.go @@ -577,6 +577,9 @@ func testMintExternalGroupKeyChantools(t *harnessTest) { mintReq2 := CopyRequest(issuableAssets[0]) mintReq2.Asset.Name = "itestbuxx-money-printer-brrr-tranche-2" mintReq2.Asset.ExternalGroupKey = externalGroupKey + mintReq2.Asset.GroupedAsset = true + mintReq2.Asset.NewGroupedAsset = false + mintReq2.Asset.GroupKey = batchAssets[0].AssetGroup.TweakedGroupKey assetReqs2 := []*mintrpc.MintAssetRequest{mintReq2} diff --git a/itest/multi_asset_group_test.go b/itest/multi_asset_group_test.go index 617df2eb27..3e3a90d690 100644 --- a/itest/multi_asset_group_test.go +++ b/itest/multi_asset_group_test.go @@ -245,7 +245,7 @@ func testMintMultiAssetGroupErrors(t *harnessTest) { groupedAsset.Asset.GroupAnchor = validAnchorName _, err = t.tapd.MintAsset(ctxb, groupedAsset) - require.ErrorContains(t.t, err, "has emission disabled") + require.ErrorContains(t.t, err, "isn't starting a new group") // Finally, we'll modify the assets to make the multi-asset group valid. validAnchor.Asset.NewGroupedAsset = true diff --git a/itest/re-issuance_test.go b/itest/re-issuance_test.go index db96bd32dc..8c4b1b037d 100644 --- a/itest/re-issuance_test.go +++ b/itest/re-issuance_test.go @@ -329,7 +329,9 @@ func testMintWithGroupKeyErrors(t *harnessTest) { reissueRequest.Asset.NewGroupedAsset = true reissueRequest.Asset.GroupedAsset = false _, err = t.tapd.MintAsset(ctxb, reissueRequest) - require.ErrorContains(t.t, err, "must disable emission to specify") + require.ErrorContains( + t.t, err, "must not create new grouped asset to specify", + ) // Restore the correct flags for a new grouped asset. reissueRequest.Asset.NewGroupedAsset = false diff --git a/rpcserver.go b/rpcserver.go index 5a9bea2558..79267977e6 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -443,19 +443,19 @@ func (r *rpcServer) MintAsset(ctx context.Context, // Using a specific group key or anchor implies disabling emission. case req.Asset.NewGroupedAsset: if specificGroupKey || specificGroupAnchor { - return nil, fmt.Errorf("must disable emission to " + - "specify a group") + return nil, fmt.Errorf("must not create new grouped " + + "asset to specify an existing group") } // A group tapscript root cannot be specified if emission is disabled. case !req.Asset.NewGroupedAsset && groupTapscriptRootSize != 0: return nil, fmt.Errorf("cannot specify a group tapscript root" + - "with emission disabled") + "when not creating a new grouped asset") // A group internal key cannot be specified if emission is disabled. case !req.Asset.NewGroupedAsset && specificGroupInternalKey: return nil, fmt.Errorf("cannot specify a group internal key" + - "with emission disabled") + "when not creating a new grouped asset") // If the asset is intended to be part of an existing group, a group key // or anchor must be specified, but not both. Neither a group tapscript @@ -473,12 +473,14 @@ func (r *rpcServer) MintAsset(ctx context.Context, if groupTapscriptRootSize != 0 { return nil, fmt.Errorf("cannot specify a group " + - "tapscript root with emission disabled") + "tapscript root when not creating a new " + + "grouped asset") } if specificGroupInternalKey { return nil, fmt.Errorf("cannot specify a group " + - "internal key with emission disabled") + "internal key when not creating a new " + + "grouped asset") } // A group was specified without GroupedAsset being set. @@ -608,8 +610,9 @@ func (r *rpcServer) MintAsset(ctx context.Context, } rpcsLog.Infof("[MintAsset]: version=%v, type=%v, name=%v, amt=%v, "+ - "enable_emission=%v", seedling.AssetVersion, seedling.AssetType, - seedling.AssetName, seedling.Amount, seedling.EnableEmission) + "new_grouped_asset=%v", seedling.AssetVersion, + seedling.AssetType, seedling.AssetName, seedling.Amount, + seedling.EnableEmission) if scriptKey != nil { seedling.ScriptKey = *scriptKey diff --git a/tapgarden/batch.go b/tapgarden/batch.go index 2d626f871e..b4234e383b 100644 --- a/tapgarden/batch.go +++ b/tapgarden/batch.go @@ -149,7 +149,7 @@ func (m *MintingBatch) validateGroupAnchor(s *Seedling) error { s.GroupAnchor) } if !anchor.EnableEmission { - return fmt.Errorf("group anchor %v has emission disabled", + return fmt.Errorf("group anchor %v isn't starting a new group", *s.GroupAnchor) } @@ -414,9 +414,9 @@ func (m *MintingBatch) validateUniCommitment(newSeedling Seedling) error { // seedling has the universe commitment flag enabled, it must // specify a re-issuable asset group key. if !newSeedling.EnableEmission { - return fmt.Errorf("the emission flag must be enabled " + - "for the first asset in a batch with the " + - "universe commitment flag enabled") + return fmt.Errorf("the 'new grouped asset' flag must " + + "be enabled for the first asset in a batch " + + "with the universe commitment flag enabled") } if !newSeedling.HasGroupKey() { diff --git a/tapgarden/planter.go b/tapgarden/planter.go index 77bb2fa3ae..4ef588778a 100644 --- a/tapgarden/planter.go +++ b/tapgarden/planter.go @@ -2668,14 +2668,19 @@ func (c *ChainPlanter) prepAssetSeedling(ctx context.Context, // If a group internal key or tapscript root is specified, emission must // also be enabled. if !req.EnableEmission { - if req.GroupInternalKey != nil { + // For re-issuing grouped assets or regular (non-grouped) + // assets, the group internal key shouldn't be set. It is, + // however, set for re-issuance with an external key, because + // the internal group key is the key we compare the external key + // against. + if req.GroupInternalKey != nil && req.ExternalKey.IsNone() { return fmt.Errorf("cannot specify group internal key " + - "without enabling emission") + "without creating a new grouped asset") } if req.GroupTapscriptRoot != nil { return fmt.Errorf("cannot specify group tapscript " + - "root without enabling emission") + "root without creating a new grouped asset") } } diff --git a/tapgarden/seedling.go b/tapgarden/seedling.go index 214e742fef..e3a2d6122b 100644 --- a/tapgarden/seedling.go +++ b/tapgarden/seedling.go @@ -183,12 +183,43 @@ func (c Seedling) validateFields() error { func (c Seedling) validateGroupKey(group asset.AssetGroup, anchorMeta *proof.MetaReveal) error { - // We must be able to sign with the group key. - if !group.GroupKey.IsLocal() { + // If an external key isn't specified but the actual group key used + // isn't local to this daemon, we won't be able to sign with it. + if c.ExternalKey.IsNone() && !group.GroupKey.IsLocal() { groupKeyBytes := c.GroupInfo.GroupPubKey.SerializeCompressed() return fmt.Errorf("can't sign with group key %x", groupKeyBytes) } + // If there is an external key defined, we need to check that it matches + // the group key. + err := fn.MapOptionZ( + c.ExternalKey, func(extKey asset.ExternalKey) error { + if group.GroupKey == nil { + return fmt.Errorf("group key is nil") + } + + if group.GroupKey.RawKey.PubKey == nil { + return fmt.Errorf("group raw key is nil") + } + + pk, err := extKey.PubKey() + if err != nil { + return fmt.Errorf("error getting external "+ + "key: %w", err) + } + + if !pk.IsEqual(group.RawKey.PubKey) { + return fmt.Errorf("external key does not " + + "match group key") + } + + return nil + }, + ) + if err != nil { + return fmt.Errorf("error validating external key: %w", err) + } + // The seedling asset type must match the group asset type. if c.AssetType != group.Genesis.Type { return fmt.Errorf("seedling type does not match "+