Skip to content

Commit fac93e6

Browse files
committed
tapgarden: add unit tests for GKR spend paths with custom scripts
Add unit tests to verify the different possible spend paths for a group key reveal witness when custom scripts are used. These tests were authored by @guggero.
1 parent 4208122 commit fac93e6

File tree

1 file changed

+254
-0
lines changed

1 file changed

+254
-0
lines changed

tapgarden/planter_test.go

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/btcsuite/btcd/txscript"
2222
"github.com/btcsuite/btcd/wire"
2323
"github.com/davecgh/go-spew/spew"
24+
"github.com/lightninglabs/lndclient"
2425
tap "github.com/lightninglabs/taproot-assets"
2526
"github.com/lightninglabs/taproot-assets/asset"
2627
"github.com/lightninglabs/taproot-assets/commitment"
@@ -2058,6 +2059,259 @@ func TestBatchedAssetIssuance(t *testing.T) {
20582059
}
20592060
}
20602061

2062+
// TestGroupKeyRevealV1WitnessWithCustomRoot tests the different possible spend
2063+
// paths for a group key reveal witness if there are custom scripts.
2064+
func TestGroupKeyRevealV1WitnessWithCustomRoot(t *testing.T) {
2065+
var (
2066+
ctx = context.Background()
2067+
mockKeyRing = tapgarden.NewMockKeyRing()
2068+
mockSigner = tapgarden.NewMockGenSigner(mockKeyRing)
2069+
txBuilder = &tapscript.GroupTxBuilder{}
2070+
txValidator = &tap.ValidatorV0{}
2071+
hashLockPreimage = []byte("foobar")
2072+
)
2073+
2074+
// We expect two keys to be derived from the mock.
2075+
go func() {
2076+
<-mockKeyRing.ReqKeys
2077+
<-mockKeyRing.ReqKeys
2078+
}()
2079+
2080+
// The internal key is for the actual internal key of the group.
2081+
internalKeyDesc, err := mockKeyRing.DeriveNextTaprootAssetKey(ctx)
2082+
require.NoError(t, err)
2083+
2084+
// The second key is used for a signature spend within a tapscript leaf
2085+
// of the custom tapscript tree.
2086+
secondKeyDesc, err := mockKeyRing.DeriveNextTaprootAssetKey(ctx)
2087+
require.NoError(t, err)
2088+
2089+
hashLockLeaf := test.ScriptHashLock(t, hashLockPreimage)
2090+
schnorrSigLeaf := test.ScriptSchnorrSig(t, secondKeyDesc.PubKey)
2091+
2092+
userRoot := txscript.AssembleTaprootScriptTree(
2093+
hashLockLeaf, schnorrSigLeaf,
2094+
).RootNode.TapHash()
2095+
2096+
spendTestCases := []struct {
2097+
name string
2098+
genWitness func(*testing.T, *asset.Asset,
2099+
asset.GroupKeyRevealV1) wire.TxWitness
2100+
}{{
2101+
name: "key spend",
2102+
genWitness: func(t *testing.T, a *asset.Asset,
2103+
gkr asset.GroupKeyRevealV1) wire.TxWitness {
2104+
2105+
genTx, prevOut, err := txBuilder.BuildGenesisTx(a)
2106+
require.NoError(t, err)
2107+
2108+
witness, err := signGroupKeyV1(
2109+
internalKeyDesc, gkr, genTx, prevOut,
2110+
mockSigner, nil,
2111+
)
2112+
require.NoError(t, err)
2113+
2114+
return witness
2115+
},
2116+
}, {
2117+
name: "script spend with preimage",
2118+
genWitness: func(t *testing.T, a *asset.Asset,
2119+
gkr asset.GroupKeyRevealV1) wire.TxWitness {
2120+
2121+
controlBlock, err := gkr.ScriptSpendControlBlock(
2122+
a.ID(),
2123+
)
2124+
require.NoError(t, err)
2125+
2126+
controlBlock.InclusionProof = bytes.Join([][]byte{
2127+
fn.ByteSlice(schnorrSigLeaf.TapHash()),
2128+
controlBlock.InclusionProof,
2129+
}, nil)
2130+
controlBlockBytes, err := controlBlock.ToBytes()
2131+
require.NoError(t, err)
2132+
2133+
// Witness is just the preimage, the script and the
2134+
// control block.
2135+
return wire.TxWitness{
2136+
hashLockPreimage,
2137+
hashLockLeaf.Script,
2138+
controlBlockBytes,
2139+
}
2140+
},
2141+
}, {
2142+
name: "script spend with signature",
2143+
genWitness: func(t *testing.T, a *asset.Asset,
2144+
gkr asset.GroupKeyRevealV1) wire.TxWitness {
2145+
2146+
genTx, prevOut, err := txBuilder.BuildGenesisTx(a)
2147+
require.NoError(t, err)
2148+
2149+
controlBlock, err := gkr.ScriptSpendControlBlock(
2150+
a.ID(),
2151+
)
2152+
require.NoError(t, err)
2153+
2154+
controlBlock.InclusionProof = bytes.Join([][]byte{
2155+
fn.ByteSlice(hashLockLeaf.TapHash()),
2156+
controlBlock.InclusionProof,
2157+
}, nil)
2158+
controlBlockBytes, err := controlBlock.ToBytes()
2159+
require.NoError(t, err)
2160+
2161+
leafToSign := &psbt.TaprootTapLeafScript{
2162+
ControlBlock: controlBlockBytes,
2163+
Script: schnorrSigLeaf.Script,
2164+
LeafVersion: txscript.BaseLeafVersion,
2165+
}
2166+
2167+
witness, err := signGroupKeyV1(
2168+
secondKeyDesc, gkr, genTx, prevOut, mockSigner,
2169+
leafToSign,
2170+
)
2171+
require.NoError(t, err)
2172+
2173+
return witness
2174+
},
2175+
}}
2176+
2177+
for _, tc := range spendTestCases {
2178+
t.Run(tc.name, func(tt *testing.T) {
2179+
randAsset := asset.RandAsset(tt, asset.Normal)
2180+
genAssetID := randAsset.ID()
2181+
groupKeyReveal, err := asset.NewGroupKeyRevealV1(
2182+
*internalKeyDesc.PubKey, genAssetID,
2183+
fn.Some(userRoot),
2184+
)
2185+
require.NoError(tt, err)
2186+
2187+
// Set the group key on the asset, since it's a randomly
2188+
// created group key otherwise.
2189+
groupPubKey, err := groupKeyReveal.GroupPubKey(
2190+
genAssetID,
2191+
)
2192+
require.NoError(tt, err)
2193+
randAsset.GroupKey = &asset.GroupKey{
2194+
RawKey: internalKeyDesc,
2195+
GroupPubKey: *groupPubKey,
2196+
TapscriptRoot: groupKeyReveal.TapscriptRoot(),
2197+
}
2198+
randAsset.PrevWitnesses = []asset.Witness{
2199+
{
2200+
PrevID: &asset.PrevID{},
2201+
},
2202+
}
2203+
2204+
witness := tc.genWitness(tt, randAsset, groupKeyReveal)
2205+
randAsset.PrevWitnesses[0].TxWitness = witness
2206+
2207+
err = txValidator.Execute(
2208+
randAsset, nil, nil, proof.MockChainLookup,
2209+
)
2210+
require.NoError(tt, err)
2211+
})
2212+
}
2213+
}
2214+
2215+
// TestGroupKeyRevealV1WitnessNoScripts tests the key spend path for a group key
2216+
// reveal witness if there are no custom scripts.
2217+
func TestGroupKeyRevealV1WitnessNoScripts(t *testing.T) {
2218+
var (
2219+
ctx = context.Background()
2220+
mockKeyRing = tapgarden.NewMockKeyRing()
2221+
mockSigner = tapgarden.NewMockGenSigner(mockKeyRing)
2222+
txBuilder = &tapscript.GroupTxBuilder{}
2223+
txValidator = &tap.ValidatorV0{}
2224+
)
2225+
2226+
// We expect just one key to be derived from the mock.
2227+
go func() {
2228+
<-mockKeyRing.ReqKeys
2229+
}()
2230+
2231+
// The internal key is for the actual internal key of the group.
2232+
internalKeyDesc, err := mockKeyRing.DeriveNextTaprootAssetKey(ctx)
2233+
require.NoError(t, err)
2234+
2235+
randAsset := asset.RandAsset(t, asset.Normal)
2236+
genAssetID := randAsset.ID()
2237+
groupKeyReveal, err := asset.NewGroupKeyRevealV1(
2238+
*internalKeyDesc.PubKey, genAssetID, fn.None[chainhash.Hash](),
2239+
)
2240+
require.NoError(t, err)
2241+
2242+
// Set the group key on the asset, since it's a randomly created group
2243+
// key otherwise.
2244+
groupPubKey, err := groupKeyReveal.GroupPubKey(genAssetID)
2245+
require.NoError(t, err)
2246+
randAsset.GroupKey = &asset.GroupKey{
2247+
RawKey: internalKeyDesc,
2248+
GroupPubKey: *groupPubKey,
2249+
TapscriptRoot: groupKeyReveal.TapscriptRoot(),
2250+
}
2251+
randAsset.PrevWitnesses = []asset.Witness{
2252+
{
2253+
PrevID: &asset.PrevID{},
2254+
},
2255+
}
2256+
2257+
genTx, prevOut, err := txBuilder.BuildGenesisTx(randAsset)
2258+
require.NoError(t, err)
2259+
2260+
witness, err := signGroupKeyV1(
2261+
internalKeyDesc, groupKeyReveal, genTx, prevOut, mockSigner,
2262+
nil,
2263+
)
2264+
require.NoError(t, err)
2265+
2266+
randAsset.PrevWitnesses[0].TxWitness = witness
2267+
2268+
err = txValidator.Execute(
2269+
randAsset, nil, nil, proof.MockChainLookup,
2270+
)
2271+
require.NoError(t, err)
2272+
}
2273+
2274+
// signGroupKeyV1 is the equivalent for asset.DeriveGroupKey but for a V1 key.
2275+
func signGroupKeyV1(keyDesc keychain.KeyDescriptor, gk asset.GroupKeyRevealV1,
2276+
genTx *wire.MsgTx, prevOut *wire.TxOut, signer asset.GenesisSigner,
2277+
leafToSign *psbt.TaprootTapLeafScript) (wire.TxWitness, error) {
2278+
2279+
signDesc := &lndclient.SignDescriptor{
2280+
KeyDesc: keyDesc,
2281+
TapTweak: gk.TapscriptRoot(),
2282+
Output: prevOut,
2283+
HashType: txscript.SigHashDefault,
2284+
InputIndex: 0,
2285+
SignMethod: input.TaprootKeySpendSignMethod,
2286+
}
2287+
2288+
if leafToSign != nil {
2289+
signDesc.SignMethod = input.TaprootScriptSpendSignMethod
2290+
signDesc.WitnessScript = leafToSign.Script
2291+
}
2292+
2293+
sig, err := signer.SignVirtualTx(signDesc, genTx, prevOut)
2294+
if err != nil {
2295+
return nil, err
2296+
}
2297+
2298+
witness := wire.TxWitness{sig.Serialize()}
2299+
2300+
// If this was a script spend, we also have to add the script itself and
2301+
// the control block to the witness, otherwise the verifier will reject
2302+
// the generated witness.
2303+
if signDesc.SignMethod == input.TaprootScriptSpendSignMethod &&
2304+
leafToSign != nil {
2305+
2306+
witness = append(
2307+
witness, signDesc.WitnessScript,
2308+
leafToSign.ControlBlock,
2309+
)
2310+
}
2311+
2312+
return witness, nil
2313+
}
2314+
20612315
func init() {
20622316
rand.Seed(time.Now().Unix())
20632317

0 commit comments

Comments
 (0)