Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions tapgarden/batch.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tapgarden

import (
"bytes"
"fmt"
"sync/atomic"
"time"
Expand Down Expand Up @@ -193,6 +194,66 @@ func (m *MintingBatch) MintingOutputKey(sibling *commitment.TapscriptPreimage) (
return m.mintingPubKey, m.taprootAssetScriptRoot, nil
}

// VerifyOutputScript recomputes a batch genesis output script from a batch key,
// tapscript sibling, and set of assets. It checks multiple tap commitment
// versions to account for legacy batches.
func VerifyOutputScript(batchKey *btcec.PublicKey, tapSibling *chainhash.Hash,
genesisScript []byte, assets []*asset.Asset) (*commitment.TapCommitment,
error) {

// Construct a TapCommitment from the batch sprouts, and verify that the
// version is correct by recomputing the genesis output script.
buildTrimmedCommitment := func(vers *commitment.TapCommitmentVersion,
assets ...*asset.Asset) (*commitment.TapCommitment, error) {

tapCommitment, err := commitment.FromAssets(vers, assets...)
if err != nil {
return nil, err
}

return commitment.TrimSplitWitnesses(vers, tapCommitment)
}

tapCommitment, err := buildTrimmedCommitment(
fn.Ptr(commitment.TapCommitmentV2), assets...,
)
if err != nil {
return nil, err
}

computedScript, err := tapscript.PayToAddrScript(
*batchKey, tapSibling, *tapCommitment,
)
if err != nil {
return nil, err
}

if !bytes.Equal(genesisScript, computedScript) {
// The batch may have used a non-V2 commitment; check against a
// non-V2 commitment.
tapCommitment, err = buildTrimmedCommitment(nil, assets...)
if err != nil {
return nil, err
}

computedScriptV0, err := tapscript.PayToAddrScript(
*batchKey, tapSibling, *tapCommitment,
)
if err != nil {
return nil, err
}

if !bytes.Equal(genesisScript, computedScriptV0) {
return nil, fmt.Errorf("invalid commitment to asset "+
"sprouts: batch %x",
batchKey.SerializeCompressed(),
)
}
}

return tapCommitment, nil
}

// genesisScript returns the script that should be placed in the minting output
// within the genesis transaction.
func (m *MintingBatch) genesisScript(sibling *commitment.TapscriptPreimage) (
Expand Down
35 changes: 20 additions & 15 deletions tapgarden/caretaker.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,21 @@ func (b *BatchCaretaker) assetCultivator() {
}
}

// extractGenesisOutpoint extracts the genesis point (the first output from the
// extractAnchorOutputIndex extracts the anchor output index from a funded
// genesis packet.
func extractAnchorOutputIndex(genesisPkt *tapsend.FundedPsbt) uint32 {
anchorOutputIndex := uint32(0)

// TODO(jhb): Does funding guarantee that minting TXs always have
// exactly two outputs? If not this func should be fallible.
if genesisPkt.ChangeOutputIndex == 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the current FundPSBT call, yeah: we'll have the target output and a change output. Once we expose batch asset chan funding though, this'll change.

anchorOutputIndex = 1
}

return anchorOutputIndex
}

// extractGenesisOutpoint extracts the genesis point (the first input from the
// genesis transaction).
func extractGenesisOutpoint(tx *wire.MsgTx) wire.OutPoint {
return tx.TxIn[0].PreviousOutPoint
Expand All @@ -436,7 +450,6 @@ func (b *BatchCaretaker) seedlingsToAssetSprouts(ctx context.Context,
b.cfg.Batch.Seedlings,
)
groupedSeedlingCount := len(groupedSeedlings)

// load seedling asset groups and check for correct group count
seedlingGroups, err := b.cfg.Log.FetchSeedlingGroups(
ctx, genesisPoint, assetOutputIndex,
Expand All @@ -453,10 +466,9 @@ func (b *BatchCaretaker) seedlingsToAssetSprouts(ctx context.Context,
seedlingGroupCount)
}

for i := range seedlingGroups {
for _, seedlingGroup := range seedlingGroups {
// check that asset group has a witness, and that the group
// has a matching seedling
seedlingGroup := seedlingGroups[i]
if len(seedlingGroup.GroupKey.Witness) == 0 {
return nil, fmt.Errorf("not all seedling groups have " +
"witnesses")
Expand Down Expand Up @@ -496,12 +508,7 @@ func (b *BatchCaretaker) seedlingsToAssetSprouts(ctx context.Context,
// build assets for ungrouped seedlings
for seedlingName := range ungroupedSeedlings {
seedling := ungroupedSeedlings[seedlingName]
assetGen := asset.Genesis{
FirstPrevOut: genesisPoint,
Tag: seedling.AssetName,
OutputIndex: assetOutputIndex,
Type: seedling.AssetType,
}
assetGen := seedling.Genesis(genesisPoint, assetOutputIndex)

// If the seedling has a meta data reveal set, then we'll bind
// that by including the hash of the meta data in the asset
Expand Down Expand Up @@ -607,11 +614,9 @@ func (b *BatchCaretaker) stateStep(currentState BatchState) (BatchState, error)
// and vice versa.
// TODO(jhb): return the anchor index instead of change? or both
// so this works for N outputs
b.anchorOutputIndex = 0
if changeOutputIndex == 0 {
b.anchorOutputIndex = 1
}

b.anchorOutputIndex = extractAnchorOutputIndex(
b.cfg.Batch.GenesisPacket,
)
genesisPoint := extractGenesisOutpoint(genesisTxPkt.UnsignedTx)

// First, we'll turn all the seedlings into actual taproot assets.
Expand Down
8 changes: 1 addition & 7 deletions tapgarden/planter.go
Original file line number Diff line number Diff line change
Expand Up @@ -587,13 +587,7 @@ func buildGroupReqs(genesisPoint wire.OutPoint, assetOutputIndex uint32,

for _, seedlingName := range orderedSeedlings {
seedling := groupSeedlings[seedlingName]

assetGen := asset.Genesis{
FirstPrevOut: genesisPoint,
Tag: seedling.AssetName,
OutputIndex: assetOutputIndex,
Type: seedling.AssetType,
}
assetGen := seedling.Genesis(genesisPoint, assetOutputIndex)

// If the seedling has a meta data reveal set, then we'll bind
// that by including the hash of the meta data in the asset
Expand Down
19 changes: 19 additions & 0 deletions tapgarden/seedling.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/sha256"
"fmt"

"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/taproot-assets/asset"
"github.com/lightninglabs/taproot-assets/proof"
"github.com/lightningnetwork/lnd/keychain"
Expand Down Expand Up @@ -199,6 +200,24 @@ func (c Seedling) validateGroupKey(group asset.AssetGroup,
return nil
}

// Genesis reconstructs the asset genesis for a seedling.
func (c Seedling) Genesis(genOutpoint wire.OutPoint,
genIndex uint32) asset.Genesis {

gen := asset.Genesis{
FirstPrevOut: genOutpoint,
Tag: c.AssetName,
OutputIndex: genIndex,
Type: c.AssetType,
}

if c.Meta != nil {
gen.MetaHash = c.Meta.MetaHash()
}

return gen
}

// HasGroupKey checks if a seedling specifies a particular group key.
func (c Seedling) HasGroupKey() bool {
return c.GroupInfo != nil && c.GroupInfo.GroupKey != nil
Expand Down