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 2 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
112 changes: 92 additions & 20 deletions asset/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/taproot-assets/fn"
"github.com/lightninglabs/taproot-assets/internal/pedersen"
"github.com/lightninglabs/taproot-assets/mssmt"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
Expand Down Expand Up @@ -953,17 +954,62 @@
// that includes the specified data. If the data is nil, the leaf will not
// contain any data but will still be a valid non-spendable script leaf.
//
// The script leaf is made non-spendable by including an OP_RETURN opcode at the
// start of the script. While the script can still be executed, it will always
// fail and cannot be used to spend funds.
func NewNonSpendableScriptLeaf(data []byte) (txscript.TapLeaf, error) {
// Construct a script builder and add the OP_RETURN opcode to the start
// of the script to ensure that the script is non-executable.
scriptBuilder := txscript.NewScriptBuilder().AddOp(txscript.OP_RETURN)
// The script leaf is made non-spendable by including an OP_RETURN/OP_CHECKSIG
// opcode at the start of the script. While the script can still be executed, it
// will always fail and cannot be used to spend funds.
Comment on lines +957 to +959
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking abstractly, about failsafe methods to prevent any accidental insertion of a NUMS without the OP_CHECKSIG:

maybe: a mutex that's called obtained when NUMS generation occurs and then is only released after prefixing the key with OP_CHECKSIG?

func NewNonSpendableScriptLeaf(version GkrV1Version,
data []byte) (txscript.TapLeaf, error) {

var firstOp byte
switch version {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

helpful switch to create flexibility

// For the OP_RETURN based version, we'll use a single OP_RETURN opcode.
case OpReturnVersion:
firstOp = txscript.OP_RETURN

// For the Pedersen commitment based version, we'll use a single
// OP_CEHCKSIG with an un-spendable key.
case PedersenVersion:
firstOp = txscript.OP_CHECKSIG

// Add the data to the script if it is provided.
if data != nil {
scriptBuilder.AddData(data)
default:
// TODO(roasbeef): auto accept of new version for forwards
// compat?
return txscript.TapLeaf{}, fmt.Errorf("unknown "+
"version %v", version)
}

// Construct a script builder and add the firstOp opcode to the start of
// the script to ensure that the script is non-executable.
scriptBuilder := txscript.NewScriptBuilder().AddOp(firstOp)
Comment on lines +981 to +983
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sound defensive positioning


switch version {
// For the OP_RETURN based version, we'll just add the data as a push
// data if provided.
case OpReturnVersion:
// Add the data to the script if it is provided.
if data != nil {
scriptBuilder.AddData(data)
}

// For the Pedersen commitment based version, things are a bit more
// involved. We'll take the asset ID then create a Pedersen commitment
// from it. We'll always add this value, as if it isn't present, then
// we'll just be committing to an empty message.
case PedersenVersion:
var msg [sha256.Size]byte
copy(msg[:], data)

// Make a Pedersen opening that uses no mask (we don't carry on
// the random value, as we don't care about hiding here). We'll
// also use the existing NUMs point.
op := pedersen.Opening{
Msg: msg,
}
commitPoint := pedersen.NewCommitment(op).Point()

commitBytes := schnorr.SerializePubKey(&commitPoint)

scriptBuilder.AddData(commitBytes)
}

// Construct script from the script builder.
Expand Down Expand Up @@ -991,8 +1037,8 @@

func NewGKRVersionRecord(version *uint8) tlv.Record {
return tlv.MakePrimitiveRecord(GKRVersion, version)
}

}

Check failure on line 1041 in asset/asset.go

View workflow job for this annotation

GitHub Actions / Lint check

unnecessary trailing newline (whitespace)
func NewGKRInternalKeyRecord(internalKey *SerializedKey) tlv.Record {
return tlv.MakePrimitiveRecord(GKRInternalKey, (*[33]byte)(internalKey))
}
Expand Down Expand Up @@ -1072,7 +1118,12 @@
// - [OP_RETURN] is a non-spendable script leaf containing the script
// `OP_RETURN`. Its presence ensures that [tweaked_custom_branch] remains
// a branch node and cannot be a valid genesis asset ID leaf.
//
// TODO(roasbeef): upgrade diagram w/ v1 and v2
type GroupKeyRevealTapscript struct {
// version is the version of the group key reveal.
version GkrV1Version

// root is the final tapscript root after all tapscript tweaks have
// been applied. The asset group key is derived from this root and the
// internal key.
Expand Down Expand Up @@ -1101,7 +1152,7 @@
// subtree root hash.
//
// nolint: lll
func NewGroupKeyTapscriptRoot(genesisAssetID ID,
func NewGroupKeyTapscriptRoot(version GkrV1Version, genesisAssetID ID,
customRoot fn.Option[chainhash.Hash]) (GroupKeyRevealTapscript, error) {

// First, we compute the tweaked custom branch hash. This hash is
Expand All @@ -1110,7 +1161,7 @@
//
// If a custom tapscript subtree root hash is provided, we use it.
// Otherwise, we default to an empty non-spendable leaf hash as well.
emptyNonSpendLeaf, err := NewNonSpendableScriptLeaf(nil)
emptyNonSpendLeaf, err := NewNonSpendableScriptLeaf(version, nil)
if err != nil {
return GroupKeyRevealTapscript{}, err
}
Expand All @@ -1125,7 +1176,7 @@
// asset ID leaf hash to compute the final tapscript root hash.
//
// Construct a non-spendable tapscript leaf for the genesis asset ID.
assetIDLeaf, err := NewNonSpendableScriptLeaf(genesisAssetID[:])
assetIDLeaf, err := NewNonSpendableScriptLeaf(version, genesisAssetID[:])
if err != nil {
return GroupKeyRevealTapscript{}, err
}
Expand All @@ -1149,6 +1200,7 @@
)

return GroupKeyRevealTapscript{
version: version,
root: rootHash,
customSubtreeRoot: customRoot,
customSubtreeInclusionProof: customSubtreeInclusionProof,
Expand All @@ -1160,7 +1212,9 @@
func (g *GroupKeyRevealTapscript) Validate(assetID ID) error {
// Compute the final tapscript root hash from the genesis asset ID and
// the custom tapscript subtree root hash.
tapscript, err := NewGroupKeyTapscriptRoot(assetID, g.customSubtreeRoot)
tapscript, err := NewGroupKeyTapscriptRoot(
g.version, assetID, g.customSubtreeRoot,
)
if err != nil {
return fmt.Errorf("failed to compute tapscript root hash: %w",
err)
Expand All @@ -1182,12 +1236,29 @@
return nil
}

// GkrV1Version is the version of the group key reveal.
//
// Version 1 is the original version that's based on an OP_RETURN.
//
// Version 2 is a follow up version that instead uses a Pedersen commitment.
type GkrV1Version = uint8

const (
// OpReturnVersion is the version of the group key reveal that uses an
// OP_RETURN.
OpReturnVersion GkrV1Version = 1

// PedersenVersion is the version of the group key reveal that uses a
// Pedersen commitment.
PedersenVersion GkrV1Version = 2
)

// GroupKeyRevealV1 is a version 1 group key reveal type for representing the
// data used to derive and verify the tweaked key used to identify an asset
// group.
type GroupKeyRevealV1 struct {
// version is the version of the group key reveal.
version uint8
version GkrV1Version

// internalKey refers to the internal key used to derive the asset
// group key. Typically, this internal key is the user's signing public
Expand All @@ -1203,21 +1274,21 @@
var _ GroupKeyReveal = (*GroupKeyRevealV1)(nil)

// NewGroupKeyRevealV1 creates a new version 1 group key reveal instance.
func NewGroupKeyRevealV1(internalKey btcec.PublicKey,
func NewGroupKeyRevealV1(version GkrV1Version, internalKey btcec.PublicKey,
genesisAssetID ID,
customRoot fn.Option[chainhash.Hash]) (GroupKeyRevealV1, error) {

// Compute the final tapscript root.
gkrTapscript, err := NewGroupKeyTapscriptRoot(
genesisAssetID, customRoot,
version, genesisAssetID, customRoot,
)
if err != nil {
return GroupKeyRevealV1{}, fmt.Errorf("failed to generate "+
"group key reveal tapscript: %w", err)
}

return GroupKeyRevealV1{
version: 1,
version: version,
internalKey: ToSerialized(&internalKey),
tapscript: gkrTapscript,
}, nil
Expand Down Expand Up @@ -1248,7 +1319,8 @@
// root.
if len(g.tapscript.customSubtreeInclusionProof) == 0 {
gkrTapscript, err := NewGroupKeyTapscriptRoot(
genesisAssetID, g.tapscript.customSubtreeRoot,
g.version, genesisAssetID,
g.tapscript.customSubtreeRoot,
)
if err != nil {
return txscript.ControlBlock{}, fmt.Errorf("failed to "+
Expand Down
14 changes: 13 additions & 1 deletion asset/group_key_reveal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
type testCaseGkrEncodeDecode struct {
testName string

version GkrV1Version

internalKey btcec.PublicKey
genesisAssetID ID
customSubtreeRoot fn.Option[chainhash.Hash]
Expand All @@ -24,7 +26,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 @@ -167,6 +170,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 GkrV1Version
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 +201,7 @@ func TestGroupKeyRevealEncodeDecodeRapid(tt *testing.T) {
// Create a new GroupKeyReveal instance from the random test
// inputs.
gkrV1, err := NewGroupKeyRevealV1(
version,
internalKey,
genesisAssetID,
customSubtreeRoot,
Expand Down
Loading
Loading