@@ -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.
8691043type Asset struct {
8701044 // Version is the Taproot Asset version of the asset.
0 commit comments