Skip to content

Commit

Permalink
commitment+proof: add CreateTapscriptProof helper function
Browse files Browse the repository at this point in the history
When creating a Tapscript proof (for a non-asset commitment output),
we need to arrange the leaf/branch preimages slightly differently
than we would for an actual sibling.
  • Loading branch information
guggero committed Apr 25, 2024
1 parent d4237ef commit f7b587a
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 4 deletions.
7 changes: 4 additions & 3 deletions commitment/taproot.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,10 @@ func NewPreimageFromBranch(branch txscript.TapBranch) TapscriptPreimage {
}
}

// TapTreeToSibling constucts a taproot sibling hash from Tapscript tree nodes,
// to be used with a TapCommitment tree root to derive a tapscript root. This
// could be multiple TapLeaf objects, or a representation of a TapBranch.
// NewPreimageFromTapscriptTreeNodes constructs a taproot sibling hash from
// Tapscript tree nodes, to be used with a TapCommitment tree root to derive a
// tapscript root. This could be multiple TapLeaf objects, or a representation
// of a TapBranch.
func NewPreimageFromTapscriptTreeNodes(
tn asset.TapscriptTreeNodes) (*TapscriptPreimage, error) {

Expand Down
112 changes: 111 additions & 1 deletion proof/taproot.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ func (p TapscriptProof) DeriveTaprootKeys(internalKey *btcec.PublicKey) (
*btcec.PublicKey, error) {

var tapscriptRoot []byte
// There're 4 possible cases for tapscript exclusion proofs:
// There're 5 possible cases for tapscript exclusion proofs:
switch {
// Two pre-images are specified, and both of the pre-images are leaf
// hashes. In this case, the tapscript tree has two elements, with both
Expand Down Expand Up @@ -539,3 +539,113 @@ func AddExclusionProofs(baseProof *BaseProofParams, finalTx *wire.MsgTx,

return nil
}

// CreateTapscriptProof creates a TapscriptProof from a list of tapscript leaves
// proving that there is no asset commitment contained in the output the proof
// is for.
func CreateTapscriptProof(leaves []txscript.TapLeaf) (*TapscriptProof, error) {
// There are 5 different possibilities. These correspond in the order
// with those in DeriveTaprootKeys.
switch len(leaves) {
// Exactly two leaves, means both of the preimages are leaves.
case 2:
left, err := commitment.NewPreimageFromLeaf(leaves[0])
if err != nil {
return nil, fmt.Errorf("error creating preimage from "+
"leaf: %w", err)
}

right, err := commitment.NewPreimageFromLeaf(leaves[1])
if err != nil {
return nil, fmt.Errorf("error creating preimage from "+
"leaf: %w", err)
}

return &TapscriptProof{
TapPreimage1: left,
TapPreimage2: right,
}, nil

// More than 3 branches, means both of the preimages are branches. We
// use the default here because all other cases from 0 to 3 are covered
// by explicit cases. We do this to retain the same order as in
// DeriveTaprootKeys.
//nolint:gocritic
default:
tree := txscript.AssembleTaprootScriptTree(leaves...)
topLeft := tree.RootNode.Left()
topRight := tree.RootNode.Right()

topLeftBranch, ok := topLeft.(txscript.TapBranch)
if !ok {
return nil, fmt.Errorf("expected left node to be a "+
"branch, got %T", topLeft)
}
leftPreimage := commitment.NewPreimageFromBranch(topLeftBranch)

topRightBranch, ok := topRight.(txscript.TapBranch)
if !ok {
return nil, fmt.Errorf("expected right node to be a "+
"branch, got %T", topRight)
}
rightPreimage := commitment.NewPreimageFromBranch(
topRightBranch,
)

return &TapscriptProof{
TapPreimage1: &leftPreimage,
TapPreimage2: &rightPreimage,
}, nil
case 3:
// Three leaves is a bit special. The AssembleTaprootScriptTree
// method puts the sole, third leaf in the right branch. But we
// need to specify the single leaf as a leaf preimage in the
// TapscriptProof. So we'll swap things below.
tree := txscript.AssembleTaprootScriptTree(leaves...)
topLeft := tree.RootNode.Left()
topRight := tree.RootNode.Right()

topLeftBranch, ok := topLeft.(txscript.TapBranch)
if !ok {
return nil, fmt.Errorf("expected left node to be a "+
"branch, got %T", topLeft)
}
leftPreimage := commitment.NewPreimageFromBranch(topLeftBranch)

topRightLeaf, ok := topRight.(txscript.TapLeaf)
if !ok {
return nil, fmt.Errorf("expected right node to be a "+
"leaf, got %T", topRight)
}
rightPreimage, err := commitment.NewPreimageFromLeaf(
topRightLeaf,
)
if err != nil {
return nil, fmt.Errorf("error creating preimage from "+
"leaf: %w", err)
}

return &TapscriptProof{
TapPreimage1: rightPreimage,
TapPreimage2: &leftPreimage,
}, nil

// Just a single leaf, means the first preimage is a leaf.
case 1:
preimage, err := commitment.NewPreimageFromLeaf(leaves[0])
if err != nil {
return nil, fmt.Errorf("error creating preimage from "+
"leaf: %w", err)
}

return &TapscriptProof{
TapPreimage1: preimage,
}, nil

// No leaves, means this is a BIP-0086 output.
case 0:
return &TapscriptProof{
Bip86: true,
}, nil
}
}
101 changes: 101 additions & 0 deletions proof/taproot_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package proof

import (
"testing"

"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/txscript"
"github.com/lightninglabs/taproot-assets/fn"
"github.com/lightninglabs/taproot-assets/internal/test"
"github.com/stretchr/testify/require"
)

// TestCreateTapscriptProof tests the creation of a TapscriptProof from a list
// of leaves.
func TestCreateTapscriptProof(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
leaves []txscript.TapLeaf
}{
{
name: "empty tree",
leaves: nil,
},
{
name: "single leaf",
leaves: []txscript.TapLeaf{
test.RandTapLeaf(nil),
},
},
{
name: "two leaves",
leaves: []txscript.TapLeaf{
test.RandTapLeaf(nil),
test.RandTapLeaf(nil),
},
},
{
name: "three leaves",
leaves: []txscript.TapLeaf{
test.RandTapLeaf(nil),
test.RandTapLeaf(nil),
},
},
{
name: "four leaves",
leaves: []txscript.TapLeaf{
test.RandTapLeaf(nil),
test.RandTapLeaf(nil),
test.RandTapLeaf(nil),
test.RandTapLeaf(nil),
},
},
{
name: "more than four leaves",
leaves: []txscript.TapLeaf{
test.RandTapLeaf(nil),
test.RandTapLeaf(nil),
test.RandTapLeaf(nil),
test.RandTapLeaf(nil),
test.RandTapLeaf(nil),
test.RandTapLeaf(nil),
test.RandTapLeaf(nil),
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tsProof, err := CreateTapscriptProof(tc.leaves)
require.NoError(t, err)

internalKey := test.RandPubKey(t)

var merkleRoot []byte
if len(tc.leaves) == 0 {
merkleRoot = []byte{}
} else {
tree := txscript.AssembleTaprootScriptTree(
tc.leaves...,
)
merkleRoot = fn.ByteSlice(
tree.RootNode.TapHash(),
)
}

expectedKey := txscript.ComputeTaprootOutputKey(
internalKey, merkleRoot,
)
expectedKey, _ = schnorr.ParsePubKey(
schnorr.SerializePubKey(expectedKey),
)

proofKey, err := tsProof.DeriveTaprootKeys(internalKey)
require.NoError(t, err)

require.Equal(t, expectedKey, proofKey)
})
}
}

0 comments on commit f7b587a

Please sign in to comment.