Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multi: add new v2 version of GKR based on Pedersen commitments #1290

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
336 changes: 236 additions & 100 deletions asset/asset.go

Large diffs are not rendered by default.

144 changes: 138 additions & 6 deletions asset/group_key_reveal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package asset

import (
"bytes"
"math/rand"
"testing"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/taproot-assets/fn"
"github.com/lightninglabs/taproot-assets/internal/test"
"github.com/stretchr/testify/require"
Expand All @@ -16,6 +18,8 @@ import (
type testCaseGkrEncodeDecode struct {
testName string

version NonSpendLeafVersion

internalKey btcec.PublicKey
genesisAssetID ID
customSubtreeRoot fn.Option[chainhash.Hash]
Expand All @@ -24,7 +28,8 @@ type testCaseGkrEncodeDecode struct {
// GroupKeyReveal generates a GroupKeyReveal instance from the test case.
func (tc testCaseGkrEncodeDecode) GroupKeyReveal() (GroupKeyReveal, error) {
gkr, err := NewGroupKeyRevealV1(
tc.internalKey, tc.genesisAssetID, tc.customSubtreeRoot,
tc.version, tc.internalKey, tc.genesisAssetID,
tc.customSubtreeRoot,
)

return &gkr, err
Expand Down Expand Up @@ -52,13 +57,15 @@ func TestGroupKeyRevealEncodeDecode(t *testing.T) {
{
testName: "no custom root",

version: OpReturnVersion,
internalKey: internalKey,
genesisAssetID: genesisAssetID,
customSubtreeRoot: fn.None[chainhash.Hash](),
},
{
testName: "with custom root",

version: PedersenVersion,
internalKey: internalKey,
genesisAssetID: genesisAssetID,
customSubtreeRoot: customSubtreeRoot,
Expand Down Expand Up @@ -95,7 +102,6 @@ func TestGroupKeyRevealEncodeDecode(t *testing.T) {
// encoding/decoding.
gkrV1, ok := gkr.(*GroupKeyRevealV1)
require.True(tt, ok)
gkrV1.tapscript.customSubtreeInclusionProof = nil

// Compare decoded group key reveal with the original.
require.Equal(tt, gkrV1, gkrDecoded)
Expand Down Expand Up @@ -157,6 +163,12 @@ func TestGroupKeyRevealEncodeDecodeRapid(tt *testing.T) {
// Generate a random internal key.
internalKeyBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).
Draw(t, "internal_key_bytes")

// The internal key bytes shouldn't be all zero.
if bytes.Equal(internalKeyBytes, make([]byte, 32)) {
return
}

_, publicKey := btcec.PrivKeyFromBytes(internalKeyBytes)
internalKey := *publicKey

Expand All @@ -167,6 +179,14 @@ func TestGroupKeyRevealEncodeDecodeRapid(tt *testing.T) {
// Randomly decide whether to include a custom script.
hasCustomScript := rapid.Bool().Draw(t, "has_custom_script")

// Version should be either 1 or 2.
var version NonSpendLeafVersion
if rapid.Bool().Draw(t, "version") {
version = OpReturnVersion
} else {
version = PedersenVersion
}

// If a custom script is included, generate a random script leaf
// and subtree root.
var customSubtreeRoot fn.Option[chainhash.Hash]
Expand All @@ -190,6 +210,7 @@ func TestGroupKeyRevealEncodeDecodeRapid(tt *testing.T) {
// Create a new GroupKeyReveal instance from the random test
// inputs.
gkrV1, err := NewGroupKeyRevealV1(
version,
internalKey,
genesisAssetID,
customSubtreeRoot,
Expand All @@ -212,10 +233,6 @@ func TestGroupKeyRevealEncodeDecodeRapid(tt *testing.T) {
)
require.NoError(t, err)

// Prepare for comparison by removing non-encoded fields from
// the original GroupKeyReveal.
gkrV1.tapscript.customSubtreeInclusionProof = nil

// Compare decoded with original.
require.Equal(t, &gkrV1, gkrDecoded)

Expand Down Expand Up @@ -259,3 +276,118 @@ func TestGroupKeyRevealEncodeDecodeRapid(tt *testing.T) {
}
})
}

// TestNonSpendableLeafScript tests that the unspendable leaf script is actually
// unspendable.
func TestNonSpendableLeafScript(t *testing.T) {
var assetID ID
_, err := rand.Read(assetID[:])
require.NoError(t, err)

internalKey := test.RandPubKey(t)

const amt = 1000

testCases := []struct {
name string

version NonSpendLeafVersion
errString string
}{

{
name: "op_return",
version: OpReturnVersion,
errString: "script returned early",
},
{
name: "pedersen",
version: PedersenVersion,
errString: "signature not empty on failed checksig",
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
// For this test, we'll just have the test leaf be the
// only element in the script tree.
testLeaf, err := NewNonSpendableScriptLeaf(
testCase.version, assetID[:],
)
require.NoError(t, err)

// From the script tree, we'll then create the taproot
// output public key.
scriptTree := txscript.AssembleTaprootScriptTree(
testLeaf,
)
rootHash := scriptTree.RootNode.TapHash()
outputKey := txscript.ComputeTaprootOutputKey(
internalKey, rootHash[:],
)

// Finally, we'll make the dummy spend transaction, and
// the output script that we'll attempt to spend.
spendTx := wire.NewMsgTx(1)
spendTx.AddTxIn(&wire.TxIn{})

leafScript, err := txscript.PayToTaprootScript(
outputKey,
)
require.NoError(t, err)

prevOuts := txscript.NewCannedPrevOutputFetcher(
leafScript, amt,
)
sigHash := txscript.NewTxSigHashes(spendTx, prevOuts)

// If this is the Pedersen variant, then we'll actually
// need to generate a signature.
var sig []byte
if testCase.version == PedersenVersion {
privKey, _ := btcec.PrivKeyFromBytes(assetID[:])

sig, err = txscript.RawTxInTapscriptSignature(
spendTx, sigHash, 0, amt, leafScript,
testLeaf, txscript.SigHashAll, privKey,
)
require.NoError(t, err)
}

proofs := scriptTree.LeafMerkleProofs[0]
ctrlBlock := proofs.ToControlBlock(internalKey)
ctrlBockBytes, err := ctrlBlock.ToBytes()
require.NoError(t, err)

// The final witness template is just the script, then
// the control block.
finalWitness := wire.TxWitness{
testLeaf.Script, ctrlBockBytes,
}

// If we have a sig, then we'll add this on as well to
// ensure that even a well crafted signature is
// rejected.
if sig != nil {
finalWitness = append(
[][]byte{sig}, finalWitness...,
)
}

spendTx.TxIn[0].Witness = finalWitness

// Finally, we'll execute the spend. This should fail if
// the leaf is actually unspendable.
vm, err := txscript.NewEngine(
leafScript, spendTx, 0,
txscript.StandardVerifyFlags, nil, sigHash,
amt, prevOuts,
)
require.NoError(t, err)

err = vm.Execute()
require.Error(t, err)
require.ErrorContains(t, err, testCase.errString)
})
}
}
Loading
Loading