Skip to content

Commit 90687e1

Browse files
committed
WIP: add group key tapscript design exploration unit test
1 parent 162e639 commit 90687e1

File tree

1 file changed

+237
-0
lines changed

1 file changed

+237
-0
lines changed

taprpc/group_key_tapscript_test.go

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
package taprpc
2+
3+
import (
4+
"bytes"
5+
"crypto/rand"
6+
"fmt"
7+
"testing"
8+
9+
"github.com/btcsuite/btcd/btcec/v2"
10+
"github.com/btcsuite/btcd/chaincfg/chainhash"
11+
"github.com/btcsuite/btcd/txscript"
12+
"github.com/lightninglabs/taproot-assets/commitment"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
func TestGroupKeyTapscript(t *testing.T) {
17+
t.Parallel()
18+
19+
// Define parameters which are available to all examples.
20+
//
21+
// Generate a random internal key.
22+
internalKey := RandInternalPubKey()
23+
24+
// Construct an asset ID leaf.
25+
assetIDLeaf := txscript.NewBaseTapLeaf(
26+
[]byte("something something OP_RETURN <asset ID>"),
27+
)
28+
assetIDLeafHash := assetIDLeaf.TapHash()
29+
30+
// Construct a custom user script leaf. This is used to validate the
31+
// control block.
32+
customScriptLeaf := txscript.NewBaseTapLeaf(
33+
[]byte("I'm a custom user script"),
34+
)
35+
36+
// ---------------------------------------------------------------------
37+
//
38+
// Example with user-provided script tree.
39+
40+
fullUserTree := &TapscriptFullTree{
41+
AllLeaves: []*TapLeaf{
42+
{
43+
Script: customScriptLeaf.Script,
44+
},
45+
},
46+
}
47+
treeNodes, err := UnmarshalTapscriptFullTree(fullUserTree)
48+
require.NoError(t, err)
49+
50+
// Validate leaves similar to IsTaprootAssetCommitmentScript().
51+
treePreimage, err := commitment.NewPreimageFromTapscriptTreeNodes(
52+
*treeNodes,
53+
)
54+
require.NoError(t, err)
55+
56+
userTreeRootHash, err := treePreimage.TapHash()
57+
require.NoError(t, err)
58+
59+
actualTaprootHash1 := tapBranchHash(assetIDLeafHash, *userTreeRootHash)
60+
fmt.Printf("Taproot hash with user tree: %v\n", actualTaprootHash1)
61+
62+
// Construct the user subtree control block.
63+
outputKey := txscript.ComputeTaprootOutputKey(
64+
&internalKey, actualTaprootHash1[:],
65+
)
66+
outputKeyIsOdd := outputKey.SerializeCompressed()[0] == 0x03
67+
68+
inclusionProof := bytes.Join(
69+
[][]byte{
70+
assetIDLeafHash[:],
71+
}, nil,
72+
)
73+
74+
userSubtreeControlBlock := txscript.ControlBlock{
75+
InternalKey: &internalKey,
76+
OutputKeyYIsOdd: outputKeyIsOdd,
77+
LeafVersion: txscript.BaseLeafVersion,
78+
InclusionProof: inclusionProof,
79+
}
80+
81+
// Ensure the custom script control block is correct by computing the
82+
// root hash given the control block and the custom script leaf.
83+
rootCheckBytes := userSubtreeControlBlock.RootHash(
84+
customScriptLeaf.Script,
85+
)
86+
87+
var rootCheck chainhash.Hash
88+
copy(rootCheck[:], rootCheckBytes)
89+
90+
require.Equal(t, actualTaprootHash1, rootCheck)
91+
92+
// ---------------------------------------------------------------------
93+
//
94+
// Example with user-provided branch only.
95+
96+
// Construct a branch from two node hashes. (A node hash can be a leaf
97+
// hash or a branch hash.) We must use two nodes so that we can be sure
98+
// that there is only one asset ID leaf in layer 1.
99+
//
100+
// In this case, we use the customScriptLeaf as one of the nodes so that
101+
// we can test the control block.
102+
userLeafHash := customScriptLeaf.TapHash()
103+
userSiblingNodeHash := chainhash.Hash([32]byte{3, 4, 5})
104+
105+
userBranch := &TapBranch{
106+
LeftTaphash: userLeafHash[:],
107+
RightTaphash: userSiblingNodeHash[:],
108+
}
109+
branchNodes, err := UnmarshalTapscriptBranch(userBranch)
110+
require.NoError(t, err)
111+
112+
branchPreimage, err := commitment.NewPreimageFromTapscriptTreeNodes(
113+
*branchNodes,
114+
)
115+
require.NoError(t, err)
116+
117+
userTreeRootHash, err = branchPreimage.TapHash()
118+
require.NoError(t, err)
119+
120+
actualTaprootHash2 := tapBranchHash(assetIDLeafHash, *userTreeRootHash)
121+
fmt.Printf("Taproot hash with user branch: %v\n", actualTaprootHash2)
122+
123+
// Construct the user subtree control block. This block targets the
124+
// custom script leaf.
125+
outputKey = txscript.ComputeTaprootOutputKey(
126+
&internalKey, actualTaprootHash2[:],
127+
)
128+
outputKeyIsOdd = outputKey.SerializeCompressed()[0] == 0x03
129+
130+
inclusionProof = bytes.Join(
131+
[][]byte{
132+
userSiblingNodeHash[:],
133+
assetIDLeafHash[:],
134+
}, nil,
135+
)
136+
137+
userSubtreeControlBlock = txscript.ControlBlock{
138+
InternalKey: &internalKey,
139+
OutputKeyYIsOdd: outputKeyIsOdd,
140+
LeafVersion: txscript.BaseLeafVersion,
141+
InclusionProof: inclusionProof,
142+
}
143+
144+
// Ensure the custom script control block is correct by computing the
145+
// root hash given the control block and the custom script leaf.
146+
rootCheckBytes = userSubtreeControlBlock.RootHash(
147+
customScriptLeaf.Script,
148+
)
149+
copy(rootCheck[:], rootCheckBytes)
150+
151+
require.Equal(t, actualTaprootHash2, rootCheck)
152+
153+
// ---------------------------------------------------------------------
154+
//
155+
// Example with an optional user-provided root hash only.
156+
157+
// Formulate an internal key leaf.
158+
internalKeyLeafHash, err := InternalKeyLeafHash(internalKey)
159+
require.NoError(t, err)
160+
161+
// This is the user's custom tapscript tree root hash. It can be set or
162+
// unset, up to the user. If unset, using [32]byte{} as the hash will be
163+
// fine.
164+
//
165+
// It can also be a leaf hash or a branch hash.
166+
userNodeHash := customScriptLeaf.TapHash()
167+
168+
branchHash := tapBranchHash(*internalKeyLeafHash, userNodeHash)
169+
actualTapscriptRootHash3 := tapBranchHash(assetIDLeafHash, branchHash)
170+
fmt.Printf("Tapscript root hash with optional user tree root: %v\n",
171+
actualTapscriptRootHash3)
172+
173+
// Construct the user subtree control block.
174+
outputKey = txscript.ComputeTaprootOutputKey(
175+
&internalKey, actualTapscriptRootHash3[:],
176+
)
177+
outputKeyIsOdd = outputKey.SerializeCompressed()[0] == 0x03
178+
179+
inclusionProof = bytes.Join(
180+
[][]byte{
181+
internalKeyLeafHash[:],
182+
assetIDLeafHash[:],
183+
}, nil,
184+
)
185+
186+
userSubtreeControlBlock = txscript.ControlBlock{
187+
InternalKey: &internalKey,
188+
OutputKeyYIsOdd: outputKeyIsOdd,
189+
LeafVersion: txscript.BaseLeafVersion,
190+
InclusionProof: inclusionProof,
191+
}
192+
193+
// Ensure the custom script control block is correct by computing the
194+
// root hash given the control block and the custom script leaf.
195+
rootCheckBytes = userSubtreeControlBlock.RootHash(
196+
customScriptLeaf.Script,
197+
)
198+
copy(rootCheck[:], rootCheckBytes)
199+
200+
require.Equal(t, actualTapscriptRootHash3, rootCheck)
201+
}
202+
203+
// Copy of commitment.tapBranchHash, should probably export.
204+
func tapBranchHash(l, r chainhash.Hash) chainhash.Hash {
205+
if bytes.Compare(l[:], r[:]) > 0 {
206+
l, r = r, l
207+
}
208+
return *chainhash.TaggedHash(chainhash.TagTapBranch, l[:], r[:])
209+
}
210+
211+
func RandInternalPubKey() btcec.PublicKey {
212+
randBytes := make([]byte, 32)
213+
_, _ = rand.Read(randBytes)
214+
215+
privateKey, _ := btcec.PrivKeyFromBytes(randBytes)
216+
pubKey := privateKey.PubKey()
217+
return *pubKey
218+
}
219+
220+
func InternalKeyLeafHash(internalKey btcec.PublicKey) (*chainhash.Hash, error) {
221+
// Construct a tapscript leaf for the internal key. Use OP_RETURN to
222+
// ensure that the script can not be executed.
223+
leafScript, err := txscript.NewScriptBuilder().
224+
AddOp(txscript.OP_RETURN).
225+
AddData(internalKey.SerializeCompressed()).
226+
Script()
227+
if err != nil {
228+
return nil, err
229+
}
230+
231+
leaf := txscript.TapLeaf{
232+
Script: leafScript,
233+
LeafVersion: txscript.BaseLeafVersion,
234+
}
235+
leafHash := leaf.TapHash()
236+
return &leafHash, nil
237+
}

0 commit comments

Comments
 (0)