Skip to content

Commit

Permalink
proof: verify group key reveal for genesis proofs
Browse files Browse the repository at this point in the history
  • Loading branch information
halseth authored and guggero committed Oct 2, 2023
1 parent 0aae52c commit cbcfdd7
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 3 deletions.
16 changes: 16 additions & 0 deletions asset/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/taproot-assets/mssmt"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/tlv"
"golang.org/x/exp/slices"
Expand Down Expand Up @@ -470,6 +471,20 @@ type GroupKeyReveal struct {
TapscriptRoot []byte
}

// GroupPubKey returns the group public key derived from the group key reveal.
func (g *GroupKeyReveal) GroupPubKey(assetID ID) (*btcec.PublicKey, error) {
rawKey, err := g.RawKey.ToPubKey()
if err != nil {
return nil, err
}

internalKey := input.TweakPubKeyWithTweak(rawKey, assetID[:])
tweakedGroupKey := txscript.ComputeTaprootOutputKey(
internalKey, g.TapscriptRoot[:],
)
return tweakedGroupKey, nil
}

// IsEqual returns true if this group key and signature are exactly equivalent
// to the passed other group key.
func (g *GroupKey) IsEqual(otherGroupKey *GroupKey) bool {
Expand Down Expand Up @@ -759,6 +774,7 @@ func (r *RawKeyGenesisSigner) SignGenesis(keyDesc keychain.KeyDescriptor,
return nil, nil, fmt.Errorf("cannot sign with key")
}

// TODO(jhb): Update to two-phase tweak
tweakedPrivKey := txscript.TweakTaprootPrivKey(
*r.privKey, initialGen.GroupKeyTweak(),
)
Expand Down
21 changes: 20 additions & 1 deletion proof/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ var (
// ErrGenesisRevealMetaRevealRequired is an error returned if an asset
// proof for a genesis asset has a non-zero meta hash, but doesn't have
// a meta reveal.
ErrGenesisRevealMetaRevealRequired = errors.New("genesis reveal meta " +
ErrGenesisRevealMetaRevealRequired = errors.New("genesis meta reveal " +
"reveal required")

// ErrGenesisRevealMetaHashMismatch is an error returned if an asset
Expand All @@ -74,6 +74,25 @@ var (
ErrGenesisRevealTypeMismatch = errors.New("genesis reveal type " +
"mismatch")

// ErrNonGenesisAssetWithGroupKeyReveal is an error returned if an asset
// proof for a non-genesis asset contains a group key reveal.
ErrNonGenesisAssetWithGroupKeyReveal = errors.New("non genesis asset " +
"has group key reveal")

// ErrGroupKeyRevealMismatch is an error returned if an asset proof for
// a genesis asset has a group key reveal that doesn't match the group
// key.
ErrGroupKeyRevealMismatch = errors.New("group key reveal doesn't " +
"match group key")

// ErrGroupKeyRevealRequired is an error returned if an asset proof for
// a genesis asset with a group key is missing a group key reveal.
ErrGroupKeyRevealRequired = errors.New("group key reveal required")

// ErrGroupKeyRequired is an error returned if an asset proof for a
// genesis asset is missing a group key when it should have one.
ErrGroupKeyRequired = errors.New("group key required")

// RegtestTestVectorName is the name of the test vector file that is
// generated/updated by an actual integration test run on regtest. It is
// exported here, so we can use it in the integration tests.
Expand Down
10 changes: 10 additions & 0 deletions proof/proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,15 @@ func genRandomGenesisWithProof(t testing.TB, assetType asset.Type,
}

assetGroupKey := asset.RandGroupKey(t, assetGenesis)
groupKeyReveal := asset.GroupKeyReveal{
RawKey: asset.ToSerialized(
assetGroupKey.RawKey.PubKey,
),
}
if assetGroupKey.TapscriptRoot != [32]byte{} {
groupKeyReveal.TapscriptRoot = assetGroupKey.TapscriptRoot[:]
}

tapCommitment, assets, err := commitment.Mint(
assetGenesis, assetGroupKey, &commitment.AssetDetails{
Type: assetType,
Expand Down Expand Up @@ -432,6 +441,7 @@ func genRandomGenesisWithProof(t testing.TB, assetType asset.Type,
ExclusionProofs: nil,
AdditionalInputs: nil,
GenesisReveal: &assetGenesis,
GroupKeyReveal: &groupKeyReveal,
}, genesisPrivKey
}

Expand Down
57 changes: 55 additions & 2 deletions proof/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,38 @@ func (p *Proof) verifyGenesisReveal() error {
return nil
}

// verifyGroupKeyReveal verifies that the group key reveal can be used to derive
// the same key as the group key specified for the asset.
func (p *Proof) verifyGroupKeyReveal() error {
groupKey := p.Asset.GroupKey
if groupKey == nil {
return ErrGroupKeyRequired
}

reveal := p.GroupKeyReveal
if reveal == nil {
return ErrGroupKeyRevealRequired
}

// TODO(jhb): Actually use this key and compare it.
_, err := reveal.GroupPubKey(p.Asset.ID())
if err != nil {
return err
}

// TODO(jhb): Enforce this check once we update SignGenesis() to
// implement the same key tweaking as in GroupPubKey(), by passing
// the assetID as a single tweak to SignOutputRaw().
// Make sure the derived key matches what we expect.
/*
if !groupKey.GroupPubKey.IsEqual(revealedKey) {
return ErrGroupKeyRevealMismatch
}
*/

return nil
}

// HeaderVerifier is a callback function which returns an error if the given
// block header is invalid (usually: not present on chain).
type HeaderVerifier func(blockHeader wire.BlockHeader, blockHeight uint32) error
Expand Down Expand Up @@ -416,7 +448,28 @@ func (p *Proof) Verify(ctx context.Context, prev *AssetSnapshot,
}
}

// 6. Either a set of asset inputs with valid witnesses is included that
// 6. Verify group key reveal for genesis assets. Not all assets have a
// group key, and should therefore not have a group key reveal. If a
// group key is present, the group key reveal must also be present.
hasGroupKeyReveal := p.GroupKeyReveal != nil
hasGroupKey := p.Asset.GroupKey != nil
switch {
case !isGenesisAsset && hasGroupKeyReveal:
return nil, ErrNonGenesisAssetWithGroupKeyReveal

case isGenesisAsset && hasGroupKey && !hasGroupKeyReveal:
return nil, ErrGroupKeyRevealRequired

case isGenesisAsset && !hasGroupKey && hasGroupKeyReveal:
return nil, ErrGroupKeyRequired

case isGenesisAsset && hasGroupKey && hasGroupKeyReveal:
if err := p.verifyGroupKeyReveal(); err != nil {
return nil, err
}
}

// 7. Either a set of asset inputs with valid witnesses is included that
// satisfy the resulting state transition or a challenge witness is
// provided as part of an ownership proof.
var splitAsset bool
Expand All @@ -433,7 +486,7 @@ func (p *Proof) Verify(ctx context.Context, prev *AssetSnapshot,
return nil, err
}

// 7. At this point we know there is an inclusion proof, which must be
// 8. At this point we know there is an inclusion proof, which must be
// a commitment proof. So we can extract the tapscript preimage directly
// from there.
tapscriptPreimage := p.InclusionProof.CommitmentProof.TapSiblingPreimage
Expand Down

0 comments on commit cbcfdd7

Please sign in to comment.