Skip to content

Commit bab8f11

Browse files
authored
Merge pull request #768 from lightninglabs/minting_tap_tree_support
tapgarden: tapscript tree support for minting anchor outputs
2 parents 8eff420 + 35c8156 commit bab8f11

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+3839
-1000
lines changed

address/address.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,9 @@ func New(version Version, genesis asset.Genesis, groupKey *btcec.PublicKey,
196196
// We can only use a tapscript sibling that is not a Taproot Asset
197197
// commitment.
198198
if tapscriptSibling != nil {
199-
if err := tapscriptSibling.VerifyNoCommitment(); err != nil {
199+
if _, err := tapscriptSibling.TapHash(); err != nil {
200200
return nil, errors.New("address: tapscript sibling " +
201-
"is a Taproot Asset commitment")
201+
"is invalid")
202202
}
203203
}
204204

address/address_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,15 @@ func randAddress(t *testing.T, net *ChainParams, v Version, groupPubKey,
4848
amount = test.RandInt[uint64]()
4949
}
5050

51-
var tapscriptSibling *commitment.TapscriptPreimage
51+
var (
52+
tapscriptSibling *commitment.TapscriptPreimage
53+
err error
54+
)
5255
if sibling {
53-
tapscriptSibling = commitment.NewPreimageFromLeaf(
56+
tapscriptSibling, err = commitment.NewPreimageFromLeaf(
5457
txscript.NewBaseTapLeaf([]byte("not a valid script")),
5558
)
59+
require.NoError(t, err)
5660
}
5761

5862
scriptKey := *pubKey

address/mock.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,11 @@ func RandAddr(t testing.TB, params *ChainParams,
7070
groupPubKey = &groupInfo.GroupPubKey
7171
groupWitness = groupInfo.Witness
7272

73-
tapscriptSibling = commitment.NewPreimageFromLeaf(
73+
var err error
74+
tapscriptSibling, err = commitment.NewPreimageFromLeaf(
7475
txscript.NewBaseTapLeaf([]byte("not a valid script")),
7576
)
77+
require.NoError(t, err)
7678
}
7779

7880
tapAddr, err := New(

address/records.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package address
22

33
import (
4+
"bytes"
45
"net/url"
56

67
"github.com/btcsuite/btcd/btcec/v2"
@@ -86,8 +87,14 @@ func newAddressTapscriptSiblingRecord(
8687
tapscriptSibling **commitment.TapscriptPreimage) tlv.Record {
8788

8889
sizeFunc := func() uint64 {
89-
// 1 byte for the type, and then the pre-image itself.
90-
return 1 + uint64(len((*tapscriptSibling).SiblingPreimage))
90+
var buf bytes.Buffer
91+
err := commitment.TapscriptPreimageEncoder(
92+
&buf, tapscriptSibling, &[8]byte{},
93+
)
94+
if err != nil {
95+
panic(err)
96+
}
97+
return uint64(len(buf.Bytes()))
9198
}
9299
return tlv.MakeDynamicRecord(
93100
addrTapscriptSiblingType, tapscriptSibling, sizeFunc,

asset/asset.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"errors"
99
"fmt"
1010
"io"
11+
"math"
1112
"reflect"
1213
"strings"
1314
"time"
@@ -22,6 +23,7 @@ import (
2223
"github.com/btcsuite/btcd/txscript"
2324
"github.com/btcsuite/btcd/wire"
2425
"github.com/lightninglabs/lndclient"
26+
"github.com/lightninglabs/taproot-assets/fn"
2527
"github.com/lightninglabs/taproot-assets/mssmt"
2628
"github.com/lightningnetwork/lnd/input"
2729
"github.com/lightningnetwork/lnd/keychain"
@@ -464,6 +466,175 @@ const (
464466
ScriptV0 ScriptVersion = 0
465467
)
466468

469+
// TapscriptTreeNodes represents the two supported ways to define a tapscript
470+
// tree to be used as a sibling for a Taproot Asset commitment, an asset group
471+
// key, or an asset script key. This type is used for interfacing with the DB,
472+
// not for supplying in a proof or key derivation. The inner fields are mutually
473+
// exclusive.
474+
type TapscriptTreeNodes struct {
475+
// leaves is created from an ordered list of TapLeaf objects and
476+
// represents a Tapscript tree.
477+
leaves *TapLeafNodes
478+
479+
// branch is created from a TapBranch and represents the tapHashes of
480+
// the child nodes of a TapBranch.
481+
branch *TapBranchNodes
482+
}
483+
484+
// GetLeaves returns an Option containing a copy of the internal TapLeafNodes,
485+
// if it exists.
486+
func GetLeaves(ttn TapscriptTreeNodes) fn.Option[TapLeafNodes] {
487+
return fn.MaybeSome(ttn.leaves)
488+
}
489+
490+
// GetBranch returns an Option containing a copy of the internal TapBranchNodes,
491+
// if it exists.
492+
func GetBranch(ttn TapscriptTreeNodes) fn.Option[TapBranchNodes] {
493+
return fn.MaybeSome(ttn.branch)
494+
}
495+
496+
// FromBranch creates a TapscriptTreeNodes object from a TapBranchNodes object.
497+
func FromBranch(tbn TapBranchNodes) TapscriptTreeNodes {
498+
return TapscriptTreeNodes{
499+
branch: &tbn,
500+
}
501+
}
502+
503+
// FromLeaves creates a TapscriptTreeNodes object from a TapLeafNodes object.
504+
func FromLeaves(tln TapLeafNodes) TapscriptTreeNodes {
505+
return TapscriptTreeNodes{
506+
leaves: &tln,
507+
}
508+
}
509+
510+
// CheckTapLeafSanity asserts that a TapLeaf script is smaller than the maximum
511+
// witness size, and that the TapLeaf version is Tapscript v0.
512+
func CheckTapLeafSanity(leaf *txscript.TapLeaf) error {
513+
if leaf == nil {
514+
return fmt.Errorf("leaf cannot be nil")
515+
}
516+
517+
if leaf.LeafVersion != txscript.BaseLeafVersion {
518+
return fmt.Errorf("tapleaf version %d not supported",
519+
leaf.LeafVersion)
520+
}
521+
522+
if len(leaf.Script) == 0 {
523+
return fmt.Errorf("tapleaf script is empty")
524+
}
525+
526+
if len(leaf.Script) >= blockchain.MaxBlockWeight {
527+
return fmt.Errorf("tapleaf script too large")
528+
}
529+
530+
return nil
531+
}
532+
533+
// TapLeafNodes represents an ordered list of TapLeaf objects, that have been
534+
// checked for their script version and size. These leaves can be stored to and
535+
// loaded from the DB.
536+
type TapLeafNodes struct {
537+
v []txscript.TapLeaf
538+
}
539+
540+
// TapTreeNodesFromLeaves sanity checks an ordered list of TapLeaf objects and
541+
// constructs a TapscriptTreeNodes object if all leaves are valid.
542+
func TapTreeNodesFromLeaves(leaves []txscript.TapLeaf) (*TapscriptTreeNodes,
543+
error) {
544+
545+
err := CheckTapLeavesSanity(leaves)
546+
if err != nil {
547+
return nil, err
548+
}
549+
550+
nodes := TapscriptTreeNodes{
551+
leaves: &TapLeafNodes{
552+
v: leaves,
553+
},
554+
}
555+
556+
return &nodes, nil
557+
}
558+
559+
// CheckTapLeavesSanity asserts that a slice of TapLeafs is below the maximum
560+
// size, and that each leaf passes a sanity check for script version and size.
561+
func CheckTapLeavesSanity(leaves []txscript.TapLeaf) error {
562+
if len(leaves) == 0 {
563+
return fmt.Errorf("no leaves given")
564+
}
565+
566+
// The maximum number of leaves we will allow for a Tapscript tree we
567+
// store is 2^15 - 1. To use a larger tree, create a TapscriptTreeNodes
568+
// object from a TapBranch instead.
569+
if len(leaves) > math.MaxInt16 {
570+
return fmt.Errorf("tapleaf count larger than %d",
571+
math.MaxInt16)
572+
}
573+
574+
// Reject any leaf not using the initial Tapscript version, or with a
575+
// script size above the maximum blocksize.
576+
for i := range leaves {
577+
err := CheckTapLeafSanity(&leaves[i])
578+
if err != nil {
579+
return err
580+
}
581+
}
582+
583+
return nil
584+
}
585+
586+
// ToLeaves returns the TapLeaf slice inside a TapLeafNodes object.
587+
func ToLeaves(l TapLeafNodes) []txscript.TapLeaf {
588+
return append([]txscript.TapLeaf{}, l.v...)
589+
}
590+
591+
// LeafNodesRootHash returns the root hash of a Tapscript tree built from the
592+
// TapLeaf nodes in a TapLeafNodes object.
593+
func LeafNodesRootHash(l TapLeafNodes) chainhash.Hash {
594+
return txscript.AssembleTaprootScriptTree(l.v...).RootNode.TapHash()
595+
}
596+
597+
// TapBranchNodesLen is the length of a TapBranch represented as a byte arrray.
598+
const TapBranchNodesLen = 64
599+
600+
// TapBranchNodes represents the tapHashes of the child nodes of a TapBranch.
601+
// These tapHashes can be stored to and loaded from the DB.
602+
type TapBranchNodes struct {
603+
left [chainhash.HashSize]byte
604+
right [chainhash.HashSize]byte
605+
}
606+
607+
// TapTreeNodesFromBranch creates a TapscriptTreeNodes object from a TapBranch.
608+
func TapTreeNodesFromBranch(branch txscript.TapBranch) TapscriptTreeNodes {
609+
return TapscriptTreeNodes{
610+
branch: &TapBranchNodes{
611+
left: branch.Left().TapHash(),
612+
right: branch.Right().TapHash(),
613+
},
614+
}
615+
}
616+
617+
// ToBranch returns an encoded TapBranchNodes object.
618+
func ToBranch(b TapBranchNodes) [][]byte {
619+
return EncodeTapBranchNodes(b)
620+
}
621+
622+
// BranchNodesRootHash returns the root hash of a Tapscript tree built from the
623+
// tapHashes stored in a TapBranchNodes object.
624+
func BranchNodesRootHash(b TapBranchNodes) chainhash.Hash {
625+
return NewTapBranchHash(b.left, b.right)
626+
}
627+
628+
// NewTapBranchHash takes the raw tap hashes of the left and right nodes and
629+
// hashes them into a branch.
630+
func NewTapBranchHash(l, r chainhash.Hash) chainhash.Hash {
631+
if bytes.Compare(l[:], r[:]) > 0 {
632+
l, r = r, l
633+
}
634+
635+
return *chainhash.TaggedHash(chainhash.TagTapBranch, l[:], r[:])
636+
}
637+
467638
// AssetGroup holds information about an asset group, including the genesis
468639
// information needed re-tweak the raw key.
469640
type AssetGroup struct {

0 commit comments

Comments
 (0)