From e0fb436622cb0bf35d0aa5bb02ee1c2175737953 Mon Sep 17 00:00:00 2001 From: Jonathan Harvey-Buschel Date: Wed, 4 Oct 2023 15:10:12 +0200 Subject: [PATCH] vm: test group anchors with script witnesses --- vm/vm_test.go | 141 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 136 insertions(+), 5 deletions(-) diff --git a/vm/vm_test.go b/vm/vm_test.go index 771d41286..16dca1ad6 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -8,9 +8,11 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/lightninglabs/lndclient" "github.com/lightninglabs/taproot-assets/asset" "github.com/lightninglabs/taproot-assets/commitment" "github.com/lightninglabs/taproot-assets/internal/test" @@ -46,6 +48,73 @@ func randAsset(t *testing.T, assetType asset.Type, ) } +func assetCustomGroupKey(t *testing.T, useHashLock, BIP86, keySpend, valid bool, + assetType asset.Type) *asset.Asset { + + t.Helper() + + genesis := asset.RandGenesis(t, assetType) + genID := genesis.ID() + scriptKey := asset.RandScriptKey(t) + protoAsset := asset.RandAssetWithValues(t, genesis, nil, scriptKey) + + groupPrivKey := test.RandPrivKey(t) + groupInternalKey := groupPrivKey.PubKey() + genSigner := asset.NewMockGenesisSigner(groupPrivKey) + genBuilder := asset.MockGroupTxBuilder{} + + // Manually create and use the singly tweaked key here, to match the + // signing behavior later when using the signing descriptor. + groupSinglyTweakedKey := input.TweakPubKeyWithTweak( + groupInternalKey, genID[:], + ) + _, _, tapLeaf, tapTweak, scriptWitness := buildTapscriptTree( + t, useHashLock, valid, groupSinglyTweakedKey, + ) + + // Default to a BIP-0086 style group key. + signDesc := lndclient.SignDescriptor{ + KeyDesc: test.PubToKeyDesc(groupInternalKey), + } + + // Update the signing descriptor and group key derivation arguments + // to match the requested group key type. + if BIP86 && keySpend { + require.Fail(t, "Cannot have both BIP 86 and key spend group "+ + "key types") + } + + switch { + case BIP86: + // Unset the created script and script witness. + tapLeaf = nil + scriptWitness = nil + + case keySpend: + // Set a tapscipt root but unset the created script and witness. + signDesc.TapTweak = tapTweak + tapLeaf = nil + scriptWitness = nil + + default: + // For a script spend, we only need to set the tapscript root + // in the sign descriptor; the script and script witness are + // already populated. + signDesc.TapTweak = tapTweak + } + + groupKey, err := asset.DeriveCustomGroupKey( + genSigner, &genBuilder, signDesc, tapLeaf, scriptWitness, + genesis, protoAsset, + ) + require.NoError(t, err) + + return asset.NewAssetNoErr( + t, genesis, protoAsset.Amount, protoAsset.LockTime, + protoAsset.RelativeLockTime, scriptKey, groupKey, + ) +} + func genTaprootKeySpend(t *testing.T, privKey btcec.PrivateKey, virtualTx *wire.MsgTx, input *asset.Asset, idx uint32) wire.TxWitness { @@ -127,6 +196,18 @@ func genesisStateTransition(assetType asset.Type, } } +func complexGroupAnchorStateTransition(useHashLock, BIP86, keySpend, valid bool, + assetType asset.Type) stateTransitionFunc { + + return func(t *testing.T) (*asset.Asset, commitment.SplitSet, + commitment.InputSet) { + + return assetCustomGroupKey( + t, useHashLock, BIP86, keySpend, valid, assetType, + ), nil, nil + } +} + func collectibleStateTransition(t *testing.T) (*asset.Asset, commitment.SplitSet, commitment.InputSet) { @@ -401,11 +482,9 @@ func splitCollectibleStateTransition(validRoot bool) stateTransitionFunc { } } -func scriptTreeSpendStateTransition(t *testing.T, useHashLock, - valid bool, sigHashType txscript.SigHashType) stateTransitionFunc { - - scriptPrivKey := test.RandPrivKey(t) - scriptInternalKey := scriptPrivKey.PubKey() +func buildTapscriptTree(t *testing.T, useHashLock, valid bool, + scriptInternalKey *btcec.PublicKey) (*txscript.TapLeaf, + *waddrmgr.Tapscript, *psbt.TaprootTapLeafScript, []byte, []byte) { // Let's create a taproot asset script now. This is a hash lock with a // simple preimage of "foobar". @@ -446,6 +525,30 @@ func scriptTreeSpendStateTransition(t *testing.T, useHashLock, } } + // Compute the final tapscript root and leaf script needed to create a + // key that includes the above tapscript tree. + tapTweak := testTapScript.ControlBlock.RootHash( + testTapScript.RevealedScript, + ) + controlBlockBytes, err := testTapScript.ControlBlock.ToBytes() + require.NoError(t, err) + + tapLeaf := &psbt.TaprootTapLeafScript{ + ControlBlock: controlBlockBytes, + Script: usedLeaf.Script, + LeafVersion: usedLeaf.LeafVersion, + } + + return usedLeaf, testTapScript, tapLeaf, tapTweak, scriptWitness +} + +func scriptTreeSpendStateTransition(t *testing.T, useHashLock, + valid bool, sigHashType txscript.SigHashType) stateTransitionFunc { + + scriptPrivKey := test.RandPrivKey(t) + usedLeaf, testTapScript, _, _, scriptWitness := buildTapscriptTree( + t, useHashLock, valid, scriptPrivKey.PubKey(), + ) scriptKey, err := testTapScript.TaprootKey() require.NoError(t, err) @@ -520,6 +623,34 @@ func TestVM(t *testing.T) { f: genesisStateTransition(asset.Collectible, false), err: newErrKind(ErrInvalidGenesisStateTransition), }, + { + name: "collectible group anchor with BIP86 group key", + f: complexGroupAnchorStateTransition( + true, true, false, false, asset.Collectible, + ), + err: nil, + }, + { + name: "collectible group anchor with key spend witness", + f: complexGroupAnchorStateTransition( + true, false, true, true, asset.Collectible, + ), + err: nil, + }, + { + name: "collectible group anchor with hash lock witness", + f: complexGroupAnchorStateTransition( + true, false, false, true, asset.Collectible, + ), + err: nil, + }, + { + name: "collectible group anchor with sig script witness", + f: complexGroupAnchorStateTransition( + false, false, false, true, asset.Collectible, + ), + err: nil, + }, { name: "invalid split collectible input", f: splitCollectibleStateTransition(false),