Skip to content

Commit

Permalink
asset: don't store non-serialized inclusion proof
Browse files Browse the repository at this point in the history
This commit removes a field from the group key reveal V1 that wasn't
serialized and calculated on demand if necessary anyway.
Re-computing it when it is needed is a smaller cost than the added
complexity of having a non-serialized field.
  • Loading branch information
guggero committed Jan 16, 2025
1 parent e83fdc8 commit 38573e7
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 63 deletions.
88 changes: 31 additions & 57 deletions asset/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -975,7 +975,7 @@ func NewGroupKeyV1FromExternal(version NonSpendLeafVersion,
"(e.g. xpub): %w", err)
}

root, err := NewGroupKeyTapscriptRoot(version, assetID, customRoot)
root, _, err := NewGroupKeyTapscriptRoot(version, assetID, customRoot)
if err != nil {
return zeroPubKey, zeroHash, fmt.Errorf("cannot derive group "+
"key reveal tapscript root: %w", err)
Expand Down Expand Up @@ -1317,26 +1317,15 @@ type GroupKeyRevealTapscript struct {
// tapscript tree. This subtree may define script spending conditions
// associated with the group key.
customSubtreeRoot fn.Option[chainhash.Hash]

// customSubtreeInclusionProof provides the inclusion proof for the
// custom tapscript subtree. It is required to spend the custom
// tapscript leaves within the tree.
//
// NOTE: This field should not be serialized as part of the group key
// reveal. It is included here to ensure it is constructed concurrently
// with the tapscript root, maintaining consistency and minimizing
// errors.
customSubtreeInclusionProof []byte
}

// NewGroupKeyTapscriptRoot computes the final tapscript root hash
// which is used to derive the asset group key. The final tapscript root
// hash is computed from the genesis asset ID and an optional custom tapscript
// subtree root hash.
//
// nolint: lll
func NewGroupKeyTapscriptRoot(version NonSpendLeafVersion, genesisAssetID ID,
customRoot fn.Option[chainhash.Hash]) (GroupKeyRevealTapscript, error) {
customRoot fn.Option[chainhash.Hash]) (GroupKeyRevealTapscript, []byte,
error) {

// First, we compute the tweaked custom branch hash. This hash is
// derived by combining the hash of a non-spendable leaf and the root
Expand All @@ -1346,7 +1335,7 @@ func NewGroupKeyTapscriptRoot(version NonSpendLeafVersion, genesisAssetID ID,
// Otherwise, we default to an empty non-spendable leaf hash as well.
emptyNonSpendLeaf, err := NewNonSpendableScriptLeaf(version, nil)
if err != nil {
return GroupKeyRevealTapscript{}, err
return GroupKeyRevealTapscript{}, nil, err
}

// Next, we'll combine the tweaked custom branch hash with the genesis
Expand All @@ -1357,7 +1346,7 @@ func NewGroupKeyTapscriptRoot(version NonSpendLeafVersion, genesisAssetID ID,
version, genesisAssetID[:],
)
if err != nil {
return GroupKeyRevealTapscript{}, err
return GroupKeyRevealTapscript{}, nil, err
}

// Compute the tweaked custom branch hash or leaf, depending on whether
Expand All @@ -1381,27 +1370,24 @@ func NewGroupKeyTapscriptRoot(version NonSpendLeafVersion, genesisAssetID ID,
emptyNonSpendLeafHash := emptyNonSpendLeaf.TapHash()
assetIDLeafHash := assetIDLeaf.TapHash()

customSubtreeInclusionProof := bytes.Join(
[][]byte{
emptyNonSpendLeafHash[:],
assetIDLeafHash[:],
}, nil,
)
customSubtreeInclusionProof := bytes.Join([][]byte{
emptyNonSpendLeafHash[:],
assetIDLeafHash[:],
}, nil)

return GroupKeyRevealTapscript{
version: version,
root: rootHash,
customSubtreeRoot: customRoot,
customSubtreeInclusionProof: customSubtreeInclusionProof,
}, nil
version: version,
root: rootHash,
customSubtreeRoot: customRoot,
}, customSubtreeInclusionProof, nil
}

// Validate checks that the group key reveal tapscript is well-formed and
// compliant.
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(
tapscript, _, err := NewGroupKeyTapscriptRoot(
g.version, assetID, g.customSubtreeRoot,
)
if err != nil {
Expand Down Expand Up @@ -1503,7 +1489,7 @@ func NewGroupKeyRevealV1(version NonSpendLeafVersion,
customRoot fn.Option[chainhash.Hash]) (GroupKeyRevealV1, error) {

// Compute the final tapscript root.
gkrTapscript, err := NewGroupKeyTapscriptRoot(
gkrTapscript, _, err := NewGroupKeyTapscriptRoot(
version, genesisAssetID, customRoot,
)
if err != nil {
Expand All @@ -1520,8 +1506,6 @@ func NewGroupKeyRevealV1(version NonSpendLeafVersion,

// ScriptSpendControlBlock returns the control block for the script spending
// path in the custom tapscript subtree.
//
// nolint: lll
func (g *GroupKeyRevealV1) ScriptSpendControlBlock(
genesisAssetID ID) (txscript.ControlBlock, error) {

Expand All @@ -1537,39 +1521,29 @@ func (g *GroupKeyRevealV1) ScriptSpendControlBlock(
outputKeyIsOdd := outputKey.SerializeCompressed()[0] ==
secp256k1.PubKeyFormatCompressedOdd

// If the custom subtree inclusion proof is nil, it may not have been
// set during decoding. Compute it, set it on the group key reveal, and
// validate both the computed tapscript root against the expected
// root.
if len(g.tapscript.customSubtreeInclusionProof) == 0 {
gkrTapscript, err := NewGroupKeyTapscriptRoot(
g.version, genesisAssetID,
g.tapscript.customSubtreeRoot,
)
if err != nil {
return txscript.ControlBlock{}, fmt.Errorf("failed to "+
"generate tapscript artifacts: %w", err)
}

// Ensure that the computed tapscript root matches the expected
// root.
if !gkrTapscript.root.IsEqual(&g.tapscript.root) {
return txscript.ControlBlock{}, fmt.Errorf("tapscript "+
"root mismatch (expected=%s, computed=%s)",
g.tapscript.root, gkrTapscript.root)
}
// We now re-calculate the group key reveal tapscript root, which also
// gives us the inclusion proof for the custom tapscript subtree.
gkrTapscript, inclusionProof, err := NewGroupKeyTapscriptRoot(
g.version, genesisAssetID, g.tapscript.customSubtreeRoot,
)
if err != nil {
return txscript.ControlBlock{}, fmt.Errorf("failed to "+
"generate tapscript artifacts: %w", err)
}

// Set the custom subtree inclusion proof on the group key
// reveal.
g.tapscript.customSubtreeInclusionProof =
gkrTapscript.customSubtreeInclusionProof
// Ensure that the computed tapscript root matches the expected
// root.
if !gkrTapscript.root.IsEqual(&g.tapscript.root) {
return txscript.ControlBlock{}, fmt.Errorf("tapscript "+
"root mismatch (expected=%s, computed=%s)",
g.tapscript.root, gkrTapscript.root)
}

return txscript.ControlBlock{
InternalKey: internalKey,
OutputKeyYIsOdd: outputKeyIsOdd,
LeafVersion: txscript.BaseLeafVersion,
InclusionProof: g.tapscript.customSubtreeInclusionProof,
InclusionProof: inclusionProof,
}, nil
}

Expand Down
5 changes: 0 additions & 5 deletions asset/group_key_reveal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,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 @@ -234,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
2 changes: 1 addition & 1 deletion tapgarden/planter.go
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,7 @@ func buildGroupReqs(genesisPoint wire.OutPoint, assetOutputIndex uint32,
// At this point, we are constructing the group
// tapscript tree root whether the
// customRootHash is defined.
tapscriptTree, err :=
tapscriptTree, _, err :=
asset.NewGroupKeyTapscriptRoot(
// TODO(guggero): Make this
// configurable in the future.
Expand Down

0 comments on commit 38573e7

Please sign in to comment.