@@ -495,6 +495,167 @@ func (c *ChainPlanter) fundGenesisPsbt(ctx context.Context,
495
495
return fundedGenesisPkt , nil
496
496
}
497
497
498
+ // filterSeedlingsWithGroup separates a set of seedlings into two sets based on
499
+ // their relation to an asset group, which has not been constructed yet.
500
+ func filterSeedlingsWithGroup (
501
+ seedlings map [string ]* Seedling ) (map [string ]* Seedling ,
502
+ map [string ]* Seedling ) {
503
+
504
+ withGroup := make (map [string ]* Seedling )
505
+ withoutGroup := make (map [string ]* Seedling )
506
+ fn .ForEachMapItem (seedlings , func (name string , seedling * Seedling ) {
507
+ switch {
508
+ case seedling .GroupInfo != nil || seedling .GroupAnchor != nil ||
509
+ seedling .EnableEmission :
510
+
511
+ withGroup [name ] = seedling
512
+
513
+ default :
514
+ withoutGroup [name ] = seedling
515
+ }
516
+ })
517
+
518
+ return withGroup , withoutGroup
519
+ }
520
+
521
+ // buildGroupReqs creates group key requests and asset group genesis TXs for
522
+ // seedlings that are part of a funded batch.
523
+ func (c * ChainPlanter ) buildGroupReqs (genesisPoint wire.OutPoint ,
524
+ assetOutputIndex uint32 ,
525
+ groupSeedlings map [string ]* Seedling ) ([]asset.GroupKeyRequest ,
526
+ []asset.GroupVirtualTx , error ) {
527
+
528
+ // Seedlings that anchor a group may be referenced by other seedlings,
529
+ // and therefore need to be mapped to sprouts first so that we derive
530
+ // the initial tweaked group key early.
531
+ orderedSeedlings := SortSeedlings (maps .Values (groupSeedlings ))
532
+ newGroups := make (map [string ]* asset.AssetGroup )
533
+ groupReqs := make ([]asset.GroupKeyRequest , 0 , len (orderedSeedlings ))
534
+ genTXs := make ([]asset.GroupVirtualTx , 0 , len (orderedSeedlings ))
535
+
536
+ for _ , seedlingName := range orderedSeedlings {
537
+ seedling := groupSeedlings [seedlingName ]
538
+
539
+ assetGen := asset.Genesis {
540
+ FirstPrevOut : genesisPoint ,
541
+ Tag : seedling .AssetName ,
542
+ OutputIndex : assetOutputIndex ,
543
+ Type : seedling .AssetType ,
544
+ }
545
+
546
+ // If the seedling has a meta data reveal set, then we'll bind
547
+ // that by including the hash of the meta data in the asset
548
+ // genesis.
549
+ if seedling .Meta != nil {
550
+ assetGen .MetaHash = seedling .Meta .MetaHash ()
551
+ }
552
+
553
+ var (
554
+ amount uint64
555
+ groupInfo * asset.AssetGroup
556
+ protoAsset * asset.Asset
557
+ err error
558
+ )
559
+
560
+ // Determine the amount for the actual asset.
561
+ switch seedling .AssetType {
562
+ case asset .Normal :
563
+ amount = seedling .Amount
564
+ case asset .Collectible :
565
+ amount = 1
566
+ }
567
+
568
+ // If the seedling has a group key specified,
569
+ // that group key was validated earlier. We need to
570
+ // sign the new genesis with that group key.
571
+ if seedling .HasGroupKey () {
572
+ groupInfo = seedling .GroupInfo
573
+ }
574
+
575
+ // If the seedling has a group anchor specified, that anchor
576
+ // was validated earlier and the corresponding group has already
577
+ // been created. We need to look up the group key and sign
578
+ // the asset genesis with that key.
579
+ if seedling .GroupAnchor != nil {
580
+ groupInfo = newGroups [* seedling .GroupAnchor ]
581
+ }
582
+
583
+ // If a group witness needs to be produced, then we will need a
584
+ // partially filled asset as part of the signing process.
585
+ if groupInfo != nil || seedling .EnableEmission {
586
+ protoAsset , err = asset .New (
587
+ assetGen , amount , 0 , 0 , seedling .ScriptKey ,
588
+ nil ,
589
+ asset .WithAssetVersion (seedling .AssetVersion ),
590
+ )
591
+ if err != nil {
592
+ return nil , nil , fmt .Errorf ("unable to create " +
593
+ "asset for group key signing: %w" , err )
594
+ }
595
+ }
596
+
597
+ if groupInfo != nil {
598
+ groupReq , err := asset .NewGroupKeyRequest (
599
+ groupInfo .GroupKey .RawKey , * groupInfo .Genesis ,
600
+ protoAsset , groupInfo .GroupKey .TapscriptRoot ,
601
+ )
602
+ if err != nil {
603
+ return nil , nil , fmt .Errorf ("unable to " +
604
+ "request asset group membership: %w" ,
605
+ err )
606
+ }
607
+
608
+ genTx , err := groupReq .BuildGroupVirtualTx (
609
+ c .cfg .GenTxBuilder ,
610
+ )
611
+ if err != nil {
612
+ return nil , nil , err
613
+ }
614
+
615
+ groupReqs = append (groupReqs , * groupReq )
616
+ genTXs = append (genTXs , * genTx )
617
+ }
618
+
619
+ // If emission is enabled, an internal key for the group should
620
+ // already be specified. Use that to derive the key group
621
+ // signature along with the tweaked key group.
622
+ if seedling .EnableEmission {
623
+ if seedling .GroupInternalKey == nil {
624
+ return nil , nil , fmt .Errorf ("unable to " +
625
+ "derive group key" )
626
+ }
627
+
628
+ groupReq , err := asset .NewGroupKeyRequest (
629
+ * seedling .GroupInternalKey , assetGen ,
630
+ protoAsset , seedling .GroupTapscriptRoot ,
631
+ )
632
+ if err != nil {
633
+ return nil , nil , fmt .Errorf ("unable to " +
634
+ "request asset group creation: %w" , err )
635
+ }
636
+
637
+ genTx , err := groupReq .BuildGroupVirtualTx (
638
+ c .cfg .GenTxBuilder ,
639
+ )
640
+ if err != nil {
641
+ return nil , nil , err
642
+ }
643
+
644
+ groupReqs = append (groupReqs , * groupReq )
645
+ genTXs = append (genTXs , * genTx )
646
+
647
+ newGroups [seedlingName ] = & asset.AssetGroup {
648
+ Genesis : & assetGen ,
649
+ GroupKey : & asset.GroupKey {
650
+ RawKey : * seedling .GroupInternalKey ,
651
+ },
652
+ }
653
+ }
654
+ }
655
+
656
+ return groupReqs , genTXs , nil
657
+ }
658
+
498
659
// freezeMintingBatch freezes a target minting batch which means that no new
499
660
// assets can be added to the batch.
500
661
func freezeMintingBatch (ctx context.Context , batchStore MintingStore ,
@@ -924,7 +1085,99 @@ func (c *ChainPlanter) fundBatch(ctx context.Context, params FundParams) error {
924
1085
return nil
925
1086
}
926
1087
927
- func (c * ChainPlanter ) sealBatch (params SealParams ) error {
1088
+ // sealBatch will verify that each grouped asset in the pending batch has an
1089
+ // asset group witness, and will attempt to create asset group witnesses when
1090
+ // possible if they are not provided. After all asset group witnesses have been
1091
+ // validated, they are saved to disk to be used by the caretaker during batch
1092
+ // finalization.
1093
+ func (c * ChainPlanter ) sealBatch (ctx context.Context , _ SealParams ) error {
1094
+ // A batch should exist with 1+ seedlings and be funded before being
1095
+ // sealed.
1096
+ if c .pendingBatch == nil {
1097
+ return fmt .Errorf ("no pending batch" )
1098
+ }
1099
+
1100
+ if len (c .pendingBatch .Seedlings ) == 0 {
1101
+ return fmt .Errorf ("no seedlings in batch" )
1102
+ }
1103
+
1104
+ if ! c .pendingBatch .IsFunded () {
1105
+ return fmt .Errorf ("batch is not funded" )
1106
+ }
1107
+
1108
+ // Filter the batch seedlings to only consider those that will become
1109
+ // grouped assets. If there are no such seedlings, then there is nothing
1110
+ // to seal and no action is needed.
1111
+ groupSeedlings , _ := filterSeedlingsWithGroup (c .pendingBatch .Seedlings )
1112
+ if len (groupSeedlings ) == 0 {
1113
+ return nil
1114
+ }
1115
+
1116
+ // Before we can build the group key requests for each seedling, we must
1117
+ // fetch the genesis point and anchor index for the batch.
1118
+ anchorOutputIndex := uint32 (0 )
1119
+ if c .pendingBatch .GenesisPacket .ChangeOutputIndex == 0 {
1120
+ anchorOutputIndex = 1
1121
+ }
1122
+
1123
+ genesisPoint := extractGenesisOutpoint (
1124
+ c .pendingBatch .GenesisPacket .Pkt .UnsignedTx ,
1125
+ )
1126
+
1127
+ // Construct the group key requests and group virtual TXs for each
1128
+ // seedling. With these we can verify provided asset group witnesses,
1129
+ // or attempt to derive asset group witnesses if needed.
1130
+ groupReqs , genTXs , err := c .buildGroupReqs (
1131
+ genesisPoint , anchorOutputIndex , groupSeedlings ,
1132
+ )
1133
+ if err != nil {
1134
+ return fmt .Errorf ("unable to build group requests: %w" , err )
1135
+ }
1136
+
1137
+ assetGroups := make ([]* asset.AssetGroup , 0 , len (groupReqs ))
1138
+ for i := 0 ; i < len (groupReqs ); i ++ {
1139
+ // Derive the asset group witness.
1140
+ groupKey , err := asset .DeriveGroupKey (
1141
+ c .cfg .GenSigner , genTXs [i ], groupReqs [i ], nil ,
1142
+ )
1143
+ if err != nil {
1144
+ return err
1145
+ }
1146
+
1147
+ // Recreate the asset with the populated group key and validate
1148
+ // the asset group witness.
1149
+ protoAsset := groupReqs [i ].NewAsset
1150
+ groupedAsset , err := asset .New (
1151
+ protoAsset .Genesis , protoAsset .Amount ,
1152
+ protoAsset .LockTime , protoAsset .RelativeLockTime ,
1153
+ protoAsset .ScriptKey , groupKey ,
1154
+ asset .WithAssetVersion (protoAsset .Version ),
1155
+ )
1156
+ if err != nil {
1157
+ return err
1158
+ }
1159
+
1160
+ err = c .cfg .TxValidator .Execute (groupedAsset , nil , nil )
1161
+ if err != nil {
1162
+ return fmt .Errorf ("unable to verify asset " +
1163
+ "group witness: %w" , err )
1164
+ }
1165
+
1166
+ newGroup := & asset.AssetGroup {
1167
+ Genesis : & groupReqs [i ].NewAsset .Genesis ,
1168
+ GroupKey : groupKey ,
1169
+ }
1170
+
1171
+ assetGroups = append (assetGroups , newGroup )
1172
+ }
1173
+
1174
+ // With all the asset group witnesses validated, we can now save them
1175
+ // to disk.
1176
+ err = c .cfg .Log .AddSeedlingGroups (ctx , genesisPoint , assetGroups )
1177
+ if err != nil {
1178
+ return fmt .Errorf ("unable to write seedling groups: %w" , err )
1179
+ }
1180
+
928
1181
return nil
929
1182
}
930
1183
0 commit comments