Skip to content

Commit

Permalink
Merge pull request #1290 from lightninglabs/pederson-group-key
Browse files Browse the repository at this point in the history
multi: add new v2 version of GKR based on Pedersen commitments
  • Loading branch information
ffranr authored Jan 16, 2025
2 parents 2e343d7 + 9733b3d commit 375a5e2
Show file tree
Hide file tree
Showing 13 changed files with 2,201 additions and 171 deletions.
365 changes: 256 additions & 109 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

0 comments on commit 375a5e2

Please sign in to comment.