Skip to content

Commit 58bfe28

Browse files
authored
Merge pull request #549 from lightninglabs/group_witness_testing
Increase group witness test coverage
2 parents c44a9cd + 290eff1 commit 58bfe28

File tree

14 files changed

+1315
-632
lines changed

14 files changed

+1315
-632
lines changed

address/mock.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,21 @@ func RandAddr(t testing.TB, params *ChainParams,
5050
}
5151

5252
var (
53+
assetVersion asset.Version
5354
groupInfo *asset.GroupKey
5455
groupPubKey *btcec.PublicKey
5556
groupWitness wire.TxWitness
5657
tapscriptSibling *commitment.TapscriptPreimage
5758
)
59+
60+
if test.RandInt[uint32]()%2 == 0 {
61+
assetVersion = asset.V1
62+
}
63+
5864
if test.RandInt[uint32]()%2 == 0 {
5965
protoAsset := asset.NewAssetNoErr(
6066
t, genesis, amount, 0, 0, scriptKey, nil,
67+
asset.WithAssetVersion(assetVersion),
6168
)
6269
groupInfo = asset.RandGroupKey(t, genesis, protoAsset)
6370
groupPubKey = &groupInfo.GroupPubKey
@@ -68,11 +75,6 @@ func RandAddr(t testing.TB, params *ChainParams,
6875
)
6976
}
7077

71-
var assetVersion asset.Version
72-
if test.RandInt[uint32]()%2 == 0 {
73-
assetVersion = asset.V1
74-
}
75-
7678
tapAddr, err := New(
7779
V0, genesis, groupPubKey, groupWitness, *scriptKey.PubKey,
7880
*internalKey.PubKey(), amount, tapscriptSibling, params,

asset/asset.go

Lines changed: 194 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/btcsuite/btcd/blockchain"
1717
"github.com/btcsuite/btcd/btcec/v2"
1818
"github.com/btcsuite/btcd/btcec/v2/schnorr"
19+
"github.com/btcsuite/btcd/btcutil/psbt"
1920
"github.com/btcsuite/btcd/txscript"
2021
"github.com/btcsuite/btcd/wire"
2122
"github.com/lightninglabs/lndclient"
@@ -491,6 +492,28 @@ type GroupKey struct {
491492
Witness wire.TxWitness
492493
}
493494

495+
// GroupKeyRequest contains the essential fields used to derive a group key.
496+
type GroupKeyRequest struct {
497+
// RawKey is the raw group key before the tweak with the genesis point
498+
// has been applied.
499+
RawKey keychain.KeyDescriptor
500+
501+
// AnchorGen is the genesis of the group anchor, which is the asset used
502+
// to derive the single tweak for the group key. For a new group key,
503+
// this will be the genesis of the new asset.
504+
AnchorGen Genesis
505+
506+
// TapscriptRoot is the root of a Tapscript tree that includes script
507+
// spend conditions for the group key. A group key with an empty
508+
// Tapscript root can only authorize reissuance with a signature.
509+
TapscriptRoot []byte
510+
511+
// NewAsset is the asset which we are requesting group membership for.
512+
// A successful request will produce a witness that authorizes this
513+
// to be a member of this asset group.
514+
NewAsset *Asset
515+
}
516+
494517
// GroupKeyReveal is a type for representing the data used to derive the tweaked
495518
// key used to identify an asset group. The final tweaked key is the result of:
496519
// TapTweak(groupInternalKey, tapscriptRoot)
@@ -798,39 +821,79 @@ func NewScriptKeyBip86(rawKey keychain.KeyDescriptor) ScriptKey {
798821
}
799822
}
800823

801-
// DeriveGroupKey derives an asset's group key based on an internal public
802-
// key descriptor, the original group asset genesis, and the asset's genesis.
803-
func DeriveGroupKey(genSigner GenesisSigner, genBuilder GenesisTxBuilder,
804-
rawKey keychain.KeyDescriptor, initialGen Genesis,
805-
newAsset *Asset) (*GroupKey, error) {
824+
// NewGroupKeyRequest constructs and validates a group key request.
825+
func NewGroupKeyRequest(internalKey keychain.KeyDescriptor, anchorGen Genesis,
826+
newAsset *Asset, scriptRoot []byte) (*GroupKeyRequest, error) {
806827

807-
// First, perform the final checks on the asset being authorized for
808-
// group membership.
809-
if newAsset == nil {
810-
return nil, fmt.Errorf("grouped asset cannot be nil")
828+
req := &GroupKeyRequest{
829+
RawKey: internalKey,
830+
AnchorGen: anchorGen,
831+
NewAsset: newAsset,
832+
TapscriptRoot: scriptRoot,
833+
}
834+
835+
err := req.Validate()
836+
if err != nil {
837+
return nil, err
838+
}
839+
840+
return req, nil
841+
}
842+
843+
// ValidateGroupKeyRequest ensures that the asset intended to be a member of an
844+
// asset group is well-formed.
845+
func (req *GroupKeyRequest) Validate() error {
846+
// Perform the final checks on the asset being authorized for group
847+
// membership.
848+
if req.NewAsset == nil {
849+
return fmt.Errorf("grouped asset cannot be nil")
850+
}
851+
852+
// The asset in the request must have the default genesis asset witness,
853+
// and no group key. Those fields can only be populated after group
854+
// witness creation.
855+
if !req.NewAsset.HasGenesisWitness() {
856+
return fmt.Errorf("asset is not a genesis asset")
857+
}
858+
859+
if req.NewAsset.GroupKey != nil {
860+
return fmt.Errorf("asset already has group key")
811861
}
812862

813-
if !newAsset.HasGenesisWitness() {
814-
return nil, fmt.Errorf("asset is not a genesis asset")
863+
if req.AnchorGen.Type != req.NewAsset.Type {
864+
return fmt.Errorf("asset group type mismatch")
815865
}
816866

817-
if newAsset.GroupKey != nil {
818-
return nil, fmt.Errorf("asset already has group key")
867+
if req.RawKey.PubKey == nil {
868+
return fmt.Errorf("missing group internal key")
819869
}
820870

821-
if initialGen.Type != newAsset.Type {
822-
return nil, fmt.Errorf("asset group type mismatch")
871+
return nil
872+
}
873+
874+
// DeriveGroupKey derives an asset's group key based on an internal public
875+
// key descriptor, the original group asset genesis, and the asset's genesis.
876+
func DeriveGroupKey(genSigner GenesisSigner, genBuilder GenesisTxBuilder,
877+
req GroupKeyRequest) (*GroupKey, error) {
878+
879+
// First, perform the final checks on the asset being authorized for
880+
// group membership.
881+
err := req.Validate()
882+
if err != nil {
883+
return nil, err
823884
}
824885

825886
// Compute the tweaked group key and set it in the asset before
826887
// creating the virtual minting transaction.
827-
genesisTweak := initialGen.ID()
828-
tweakedGroupKey, err := GroupPubKey(rawKey.PubKey, genesisTweak[:], nil)
888+
genesisTweak := req.AnchorGen.ID()
889+
tweakedGroupKey, err := GroupPubKey(
890+
req.RawKey.PubKey, genesisTweak[:], nil,
891+
)
829892
if err != nil {
830893
return nil, fmt.Errorf("cannot tweak group key: %w", err)
831894
}
832895

833-
assetWithGroup := newAsset.Copy()
896+
assetWithGroup := req.NewAsset.Copy()
834897
assetWithGroup.GroupKey = &GroupKey{
835898
GroupPubKey: *tweakedGroupKey,
836899
}
@@ -846,7 +909,7 @@ func DeriveGroupKey(genSigner GenesisSigner, genBuilder GenesisTxBuilder,
846909
// minting transaction. This is restricted to group keys with an empty
847910
// tapscript root and key path spends.
848911
signDesc := &lndclient.SignDescriptor{
849-
KeyDesc: rawKey,
912+
KeyDesc: req.RawKey,
850913
SingleTweak: genesisTweak[:],
851914
SignMethod: input.TaprootKeySpendBIP0086SignMethod,
852915
Output: prevOut,
@@ -859,12 +922,123 @@ func DeriveGroupKey(genSigner GenesisSigner, genBuilder GenesisTxBuilder,
859922
}
860923

861924
return &GroupKey{
862-
RawKey: rawKey,
925+
RawKey: req.RawKey,
863926
GroupPubKey: *tweakedGroupKey,
864927
Witness: wire.TxWitness{sig.Serialize()},
865928
}, nil
866929
}
867930

931+
// DeriveCustomGroupKey derives an asset's group key based on a signing
932+
// descriptor, the original group asset genesis, and the asset's genesis.
933+
func DeriveCustomGroupKey(genSigner GenesisSigner, genBuilder GenesisTxBuilder,
934+
req GroupKeyRequest, tapLeaf *psbt.TaprootTapLeafScript,
935+
scriptWitness []byte) (*GroupKey, error) {
936+
937+
// First, perform the final checks on the asset being authorized for
938+
// group membership.
939+
err := req.Validate()
940+
if err != nil {
941+
return nil, err
942+
}
943+
944+
// Compute the tweaked group key and set it in the asset before
945+
// creating the virtual minting transaction.
946+
genesisTweak := req.AnchorGen.ID()
947+
tweakedGroupKey, err := GroupPubKey(
948+
req.RawKey.PubKey, genesisTweak[:], req.TapscriptRoot,
949+
)
950+
if err != nil {
951+
return nil, fmt.Errorf("cannot tweak group key: %w", err)
952+
}
953+
954+
assetWithGroup := req.NewAsset.Copy()
955+
assetWithGroup.GroupKey = &GroupKey{
956+
GroupPubKey: *tweakedGroupKey,
957+
}
958+
959+
// Exit early if a group witness is already given, since we don't need
960+
// to construct a virtual TX nor produce a signature.
961+
if scriptWitness != nil {
962+
if tapLeaf == nil {
963+
return nil, fmt.Errorf("need tap leaf with group " +
964+
"script witness")
965+
}
966+
967+
witness := wire.TxWitness{
968+
scriptWitness, tapLeaf.Script, tapLeaf.ControlBlock,
969+
}
970+
971+
return &GroupKey{
972+
RawKey: req.RawKey,
973+
GroupPubKey: *tweakedGroupKey,
974+
TapscriptRoot: req.TapscriptRoot,
975+
Witness: witness,
976+
}, nil
977+
}
978+
979+
// Build the virtual transaction that represents the minting of the new
980+
// asset, which will be signed to generate the group witness.
981+
genesisTx, prevOut, err := genBuilder.BuildGenesisTx(assetWithGroup)
982+
if err != nil {
983+
return nil, fmt.Errorf("cannot build virtual tx: %w", err)
984+
}
985+
986+
// Populate the signing descriptor needed to sign the virtual minting
987+
// transaction.
988+
signDesc := &lndclient.SignDescriptor{
989+
KeyDesc: req.RawKey,
990+
SingleTweak: genesisTweak[:],
991+
TapTweak: req.TapscriptRoot,
992+
Output: prevOut,
993+
HashType: txscript.SigHashDefault,
994+
InputIndex: 0,
995+
}
996+
997+
// There are three possible signing cases: BIP-0086 key spend path, key
998+
// spend path with a script root, and script spend path.
999+
switch {
1000+
// If there is no tapscript root, we're doing a BIP-0086 key spend.
1001+
case len(signDesc.TapTweak) == 0:
1002+
signDesc.SignMethod = input.TaprootKeySpendBIP0086SignMethod
1003+
1004+
// No leaf means we're not signing a specific script, so this is the key
1005+
// spend path with a tapscript root.
1006+
case len(signDesc.TapTweak) != 0 && tapLeaf == nil:
1007+
signDesc.SignMethod = input.TaprootKeySpendSignMethod
1008+
1009+
// One leaf hash and a merkle root means we're signing a specific
1010+
// script.
1011+
case len(signDesc.TapTweak) != 0 && tapLeaf != nil:
1012+
signDesc.SignMethod = input.TaprootScriptSpendSignMethod
1013+
signDesc.WitnessScript = tapLeaf.Script
1014+
1015+
default:
1016+
return nil, fmt.Errorf("bad sign descriptor for group key")
1017+
}
1018+
1019+
sig, err := genSigner.SignVirtualTx(signDesc, genesisTx, prevOut)
1020+
if err != nil {
1021+
return nil, err
1022+
}
1023+
1024+
witness := wire.TxWitness{sig.Serialize()}
1025+
1026+
// If this was a script spend, we also have to add the script itself and
1027+
// the control block to the witness, otherwise the verifier will reject
1028+
// the generated witness.
1029+
if signDesc.SignMethod == input.TaprootScriptSpendSignMethod {
1030+
witness = append(witness, signDesc.WitnessScript)
1031+
witness = append(witness, tapLeaf.ControlBlock)
1032+
}
1033+
1034+
return &GroupKey{
1035+
RawKey: signDesc.KeyDesc,
1036+
GroupPubKey: *tweakedGroupKey,
1037+
TapscriptRoot: signDesc.TapTweak,
1038+
Witness: witness,
1039+
}, nil
1040+
}
1041+
8681042
// Asset represents a Taproot asset.
8691043
type Asset struct {
8701044
// Version is the Taproot Asset version of the asset.

asset/asset_test.go

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -632,8 +632,25 @@ func TestAssetGroupKey(t *testing.T) {
632632
// TweakTaprootPrivKey modifies the private key that is passed in! We
633633
// need to provide a copy to arrive at the same result.
634634
protoAsset := NewAssetNoErr(t, g, 1, 0, 0, fakeScriptKey, nil)
635-
keyGroup, err := DeriveGroupKey(
636-
genSigner, &genBuilder, fakeKeyDesc, g, protoAsset,
635+
groupReq := NewGroupKeyRequestNoErr(t, fakeKeyDesc, g, protoAsset, nil)
636+
keyGroup, err := DeriveGroupKey(genSigner, &genBuilder, *groupReq)
637+
require.NoError(t, err)
638+
639+
require.Equal(
640+
t, schnorr.SerializePubKey(tweakedKey.PubKey()),
641+
schnorr.SerializePubKey(&keyGroup.GroupPubKey),
642+
)
643+
644+
// We should also be able to reproduce the correct tweak with a non-nil
645+
// tapscript root.
646+
tapTweak := test.RandBytes(32)
647+
tweakedKey = txscript.TweakTaprootPrivKey(*internalKey, tapTweak)
648+
649+
groupReq = NewGroupKeyRequestNoErr(
650+
t, test.PubToKeyDesc(privKey.PubKey()), g, protoAsset, tapTweak,
651+
)
652+
keyGroup, err = DeriveCustomGroupKey(
653+
genSigner, &genBuilder, *groupReq, nil, nil,
637654
)
638655
require.NoError(t, err)
639656

@@ -683,35 +700,40 @@ func TestDeriveGroupKey(t *testing.T) {
683700
groupedProtoAsset.GroupKey = &GroupKey{
684701
GroupPubKey: *groupPub,
685702
}
703+
groupReq := GroupKeyRequest{
704+
RawKey: groupKeyDesc,
705+
AnchorGen: baseGen,
706+
}
686707

687708
// A prototype asset is required for building the genesis virtual TX.
688-
_, err := DeriveGroupKey(
689-
genSigner, &genBuilder, groupKeyDesc, baseGen, nil,
690-
)
691-
require.Error(t, err)
709+
_, err := DeriveGroupKey(genSigner, &genBuilder, groupReq)
710+
require.ErrorContains(t, err, "grouped asset cannot be nil")
692711

693712
// The prototype asset must have a genesis witness.
694-
_, err = DeriveGroupKey(
695-
genSigner, &genBuilder, groupKeyDesc, baseGen, nonGenProtoAsset,
696-
)
697-
require.Error(t, err)
713+
groupReq.NewAsset = nonGenProtoAsset
714+
_, err = DeriveGroupKey(genSigner, &genBuilder, groupReq)
715+
require.ErrorContains(t, err, "asset is not a genesis asset")
698716

699717
// The prototype asset must not have a group key set.
700-
_, err = DeriveGroupKey(
701-
genSigner, &genBuilder, groupKeyDesc, baseGen, groupedProtoAsset,
702-
)
703-
require.Error(t, err)
718+
groupReq.NewAsset = groupedProtoAsset
719+
_, err = DeriveGroupKey(genSigner, &genBuilder, groupReq)
720+
require.ErrorContains(t, err, "asset already has group key")
704721

705722
// The anchor genesis used for signing must have the same asset type
706723
// as the prototype asset being signed.
707-
_, err = DeriveGroupKey(
708-
genSigner, &genBuilder, groupKeyDesc, collectGen, protoAsset,
709-
)
710-
require.Error(t, err)
711-
712-
groupKey, err := DeriveGroupKey(
713-
genSigner, &genBuilder, groupKeyDesc, baseGen, protoAsset,
714-
)
724+
groupReq.AnchorGen = collectGen
725+
groupReq.NewAsset = protoAsset
726+
_, err = DeriveGroupKey(genSigner, &genBuilder, groupReq)
727+
require.ErrorContains(t, err, "asset group type mismatch")
728+
729+
// The group key request must include an internal key.
730+
groupReq.AnchorGen = baseGen
731+
groupReq.RawKey.PubKey = nil
732+
_, err = DeriveGroupKey(genSigner, &genBuilder, groupReq)
733+
require.ErrorContains(t, err, "missing group internal key")
734+
735+
groupReq.RawKey = groupKeyDesc
736+
groupKey, err := DeriveGroupKey(genSigner, &genBuilder, groupReq)
715737
require.NoError(t, err)
716738
require.NotNil(t, groupKey)
717739
}

0 commit comments

Comments
 (0)