Skip to content

Commit

Permalink
Merge pull request #1235 from lightninglabs/groupkeyreveal-version-re…
Browse files Browse the repository at this point in the history
…factor

Refactor GroupKeyReveal for multi-version support
  • Loading branch information
ffranr authored Dec 5, 2024
2 parents 651ab88 + 2fa1fb5 commit 3d3b254
Show file tree
Hide file tree
Showing 14 changed files with 135 additions and 79 deletions.
94 changes: 74 additions & 20 deletions asset/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -918,49 +918,96 @@ type GroupVirtualTx struct {
TweakedKey btcec.PublicKey
}

// GroupKeyReveal is a type for representing the data used to derive the tweaked
// key used to identify an asset group. The final tweaked key is the result of:
// TapTweak(groupInternalKey, tapscriptRoot)
type GroupKeyReveal struct {
// GroupKeyReveal represents the data used to derive the adjusted key that
// uniquely identifies an asset group.
type GroupKeyReveal interface {
// RawKey returns the raw key of the group key reveal.
RawKey() SerializedKey

// SetRawKey sets the raw key of the group key reveal.
SetRawKey(SerializedKey)

// TapscriptRoot returns the tapscript root of the group key reveal.
TapscriptRoot() []byte

// SetTapscriptRoot sets the tapscript root of the group key reveal.
SetTapscriptRoot([]byte)

// GroupPubKey returns the group public key derived from the group key
// reveal.
GroupPubKey(assetID ID) (*btcec.PublicKey, error)
}

// GroupKeyRevealV0 is a version 0 group key reveal type for representing the
// data used to derive the tweaked key used to identify an asset group. The
// final tweaked key is the result of: TapTweak(groupInternalKey, tapscriptRoot)
type GroupKeyRevealV0 struct {
// RawKey is the public key that is tweaked twice to derive the final
// tweaked group key. The final tweaked key is the result of:
// internalKey = rawKey + singleTweak * G
// tweakedGroupKey = TapTweak(internalKey, tapTweak)
RawKey SerializedKey
rawKey SerializedKey

// TapscriptRoot is the root of the Tapscript tree that commits to all
// script spend conditions for the group key. Instead of spending an
// asset, these scripts are used to define witnesses more complex than
// a Schnorr signature for reissuing assets. This is either empty/nil or
// a 32-byte hash.
TapscriptRoot []byte
tapscriptRoot []byte
}

// PendingGroupWitness specifies the asset group witness for an asset seedling
// in an unsealed minting batch.
type PendingGroupWitness struct {
GenID ID
Witness wire.TxWitness
// Ensure that GroupKeyRevealV0 implements the GroupKeyReveal interface.
var _ GroupKeyReveal = (*GroupKeyRevealV0)(nil)

// NewGroupKeyRevealV0 creates a new version 0 group key reveal instance.
func NewGroupKeyRevealV0(rawKey SerializedKey,
tapscriptRoot []byte) GroupKeyReveal {

return &GroupKeyRevealV0{
rawKey: rawKey,
tapscriptRoot: tapscriptRoot,
}
}

// RawKey returns the raw key of the group key reveal.
func (g *GroupKeyRevealV0) RawKey() SerializedKey {
return g.rawKey
}

// SetRawKey sets the raw key of the group key reveal.
func (g *GroupKeyRevealV0) SetRawKey(rawKey SerializedKey) {
g.rawKey = rawKey
}

// TapscriptRoot returns the tapscript root of the group key reveal.
func (g *GroupKeyRevealV0) TapscriptRoot() []byte {
return g.tapscriptRoot
}

// SetTapscriptRoot sets the tapscript root of the group key reveal.
func (g *GroupKeyRevealV0) SetTapscriptRoot(tapscriptRoot []byte) {
g.tapscriptRoot = tapscriptRoot
}

// 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()
func (g *GroupKeyRevealV0) GroupPubKey(assetID ID) (*btcec.PublicKey, error) {
rawKey, err := g.RawKey().ToPubKey()
if err != nil {
return nil, fmt.Errorf("group reveal raw key invalid: %w", err)
}

return GroupPubKey(rawKey, assetID[:], g.TapscriptRoot)
return GroupPubKeyV0(rawKey, assetID[:], g.TapscriptRoot())
}

// GroupPubKey derives a tweaked group key from a public key and two tweaks;
// the single tweak is the asset ID of the group anchor asset, and the tapTweak
// is the root of a tapscript tree that commits to script-based conditions for
// reissuing assets as part of this asset group. The tweaked key is defined by:
// GroupPubKeyV0 derives a version 0 tweaked group key from a public key and two
// tweaks; the single tweak is the asset ID of the group anchor asset, and the
// tapTweak is the root of a tapscript tree that commits to script-based
// conditions for reissuing assets as part of this asset group. The tweaked key
// is defined by:
//
// internalKey = rawKey + singleTweak * G
// tweakedGroupKey = TapTweak(internalKey, tapTweak)
func GroupPubKey(rawKey *btcec.PublicKey, singleTweak, tapTweak []byte) (
func GroupPubKeyV0(rawKey *btcec.PublicKey, singleTweak, tapTweak []byte) (
*btcec.PublicKey, error) {

if len(singleTweak) != sha256.Size {
Expand Down Expand Up @@ -1371,7 +1418,7 @@ func (req *GroupKeyRequest) BuildGroupVirtualTx(genBuilder GenesisTxBuilder) (
// Compute the tweaked group key and set it in the asset before
// creating the virtual minting transaction.
genesisTweak := req.AnchorGen.ID()
tweakedGroupKey, err := GroupPubKey(
tweakedGroupKey, err := GroupPubKeyV0(
req.RawKey.PubKey, genesisTweak[:], req.TapscriptRoot,
)
if err != nil {
Expand Down Expand Up @@ -1491,6 +1538,13 @@ func DeriveGroupKey(genSigner GenesisSigner, genTx GroupVirtualTx,
}, nil
}

// PendingGroupWitness specifies the asset group witness for an asset seedling
// in an unsealed minting batch.
type PendingGroupWitness struct {
GenID ID
Witness wire.TxWitness
}

// Asset represents a Taproot asset.
type Asset struct {
// Version is the Taproot Asset version of the asset.
Expand Down
4 changes: 2 additions & 2 deletions asset/asset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -920,10 +920,10 @@ func TestAssetGroupKey(t *testing.T) {

// Group key tweaking should fail when given invalid tweaks.
badTweak := test.RandBytes(33)
_, err = GroupPubKey(groupPub, badTweak, badTweak)
_, err = GroupPubKeyV0(groupPub, badTweak, badTweak)
require.Error(t, err)

_, err = GroupPubKey(groupPub, groupTweak[:], badTweak)
_, err = GroupPubKeyV0(groupPub, groupTweak[:], badTweak)
require.Error(t, err)
}

Expand Down
14 changes: 6 additions & 8 deletions asset/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -971,13 +971,14 @@ func (tgr *TestGenesisReveal) ToGenesisReveal(t testing.TB) *Genesis {
}

func NewTestFromGroupKeyReveal(t testing.TB,
gkr *GroupKeyReveal) *TestGroupKeyReveal {
gkr GroupKeyReveal) *TestGroupKeyReveal {

t.Helper()

rawKey := gkr.RawKey()
return &TestGroupKeyReveal{
RawKey: hex.EncodeToString(gkr.RawKey[:]),
TapscriptRoot: hex.EncodeToString(gkr.TapscriptRoot),
RawKey: hex.EncodeToString(rawKey[:]),
TapscriptRoot: hex.EncodeToString(gkr.TapscriptRoot()),
}
}

Expand All @@ -986,15 +987,12 @@ type TestGroupKeyReveal struct {
TapscriptRoot string `json:"tapscript_root"`
}

func (tgkr *TestGroupKeyReveal) ToGroupKeyReveal(t testing.TB) *GroupKeyReveal {
func (tgkr *TestGroupKeyReveal) ToGroupKeyReveal(t testing.TB) GroupKeyReveal {
t.Helper()

rawKey := test.ParsePubKey(t, tgkr.RawKey)
tapscriptRoot, err := hex.DecodeString(tgkr.TapscriptRoot)
require.NoError(t, err)

return &GroupKeyReveal{
RawKey: ToSerialized(rawKey),
TapscriptRoot: tapscriptRoot,
}
return NewGroupKeyRevealV0(ToSerialized(rawKey), tapscriptRoot)
}
2 changes: 1 addition & 1 deletion itest/assertions.go
Original file line number Diff line number Diff line change
Expand Up @@ -1403,7 +1403,7 @@ func AssertGroupAnchor(t *testing.T, anchorGen *asset.Genesis,

// TODO(jhb): add tapscript root support
anchorTweak := anchorGen.ID()
computedGroupPubKey, err := asset.GroupPubKey(
computedGroupPubKey, err := asset.GroupPubKeyV0(
internalPubKey, anchorTweak[:], nil,
)
require.NoError(t, err)
Expand Down
22 changes: 12 additions & 10 deletions proof/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,13 +467,13 @@ func GenesisRevealDecoder(r io.Reader, val any, buf *[8]byte, l uint64) error {
}

func GroupKeyRevealEncoder(w io.Writer, val any, buf *[8]byte) error {
if t, ok := val.(**asset.GroupKeyReveal); ok {
key := &(*t).RawKey
if err := asset.SerializedKeyEncoder(w, key, buf); err != nil {
if t, ok := val.(*asset.GroupKeyReveal); ok {
key := (*t).RawKey()
if err := asset.SerializedKeyEncoder(w, &key, buf); err != nil {
return err
}
root := &(*t).TapscriptRoot
return tlv.EVarBytes(w, root, buf)
root := (*t).TapscriptRoot()
return tlv.EVarBytes(w, &root, buf)
}

return tlv.NewTypeForEncodingErr(val, "GroupKeyReveal")
Expand All @@ -489,20 +489,22 @@ func GroupKeyRevealDecoder(r io.Reader, val any, buf *[8]byte, l uint64) error {
ErrProofInvalid)
}

if typ, ok := val.(**asset.GroupKeyReveal); ok {
var reveal asset.GroupKeyReveal
if typ, ok := val.(*asset.GroupKeyReveal); ok {
var rawKey asset.SerializedKey
err := asset.SerializedKeyDecoder(
r, &reveal.RawKey, buf, btcec.PubKeyBytesLenCompressed,
r, &rawKey, buf, btcec.PubKeyBytesLenCompressed,
)
if err != nil {
return err
}
remaining := l - btcec.PubKeyBytesLenCompressed
err = tlv.DVarBytes(r, &reveal.TapscriptRoot, buf, remaining)
var tapscriptRoot []byte
err = tlv.DVarBytes(r, &tapscriptRoot, buf, remaining)
if err != nil {
return err
}
*typ = &reveal

*typ = asset.NewGroupKeyRevealV0(rawKey, tapscriptRoot)
return nil
}
return tlv.NewTypeForEncodingErr(val, "GroupKeyReveal")
Expand Down
11 changes: 5 additions & 6 deletions proof/mint.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,12 +388,11 @@ func committedProofs(baseProof *Proof, tapTreeRoot *commitment.TapCommitment,

err := groupAnchorVerifier(&newAsset.Genesis, groupKey)
if err == nil {
groupReveal := &asset.GroupKeyReveal{
RawKey: asset.ToSerialized(
groupKey.RawKey.PubKey,
),
TapscriptRoot: groupKey.TapscriptRoot,
}
rawKey := asset.ToSerialized(
groupKey.RawKey.PubKey,
)
groupReveal := asset.NewGroupKeyRevealV0(
rawKey, groupKey.TapscriptRoot)
assetProof.GroupKeyReveal = groupReveal
}
}
Expand Down
10 changes: 5 additions & 5 deletions proof/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ func RandProof(t testing.TB, genesis asset.Genesis,
t, genesis, 1, 0, 0, tweakedScriptKey, nil,
)
groupKey := asset.RandGroupKey(t, genesis, protoAsset)
groupReveal := asset.GroupKeyReveal{
RawKey: asset.ToSerialized(&groupKey.GroupPubKey),
TapscriptRoot: test.RandBytes(32),
}
groupReveal := asset.NewGroupKeyRevealV0(
asset.ToSerialized(&groupKey.GroupPubKey),
test.RandBytes(32),
)

amount := uint64(1)
mintCommitment, assets, err := commitment.Mint(
Expand Down Expand Up @@ -146,7 +146,7 @@ func RandProof(t testing.TB, genesis asset.Genesis,
},
ChallengeWitness: wire.TxWitness{[]byte("foo"), []byte("bar")},
GenesisReveal: &genesis,
GroupKeyReveal: &groupReveal,
GroupKeyReveal: groupReveal,
}
}

Expand Down
12 changes: 7 additions & 5 deletions proof/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,11 +301,13 @@ type Proof struct {
// re-derivation of the asset group key.
GenesisReveal *asset.Genesis

// GroupKeyReveal is an optional set of bytes that represent the public
// key and Tapscript root used to derive the final tweaked group key for
// the asset group. This field must be provided for issuance proofs of
// grouped assets.
GroupKeyReveal *asset.GroupKeyReveal
// GroupKeyReveal contains the data required to derive the final tweaked
// group key for an asset group.
//
// NOTE: This field is mandatory for the group anchor (i.e., the initial
// minting tranche of an asset group). Subsequent minting tranches
// require only a valid signature for the previously revealed group key.
GroupKeyReveal asset.GroupKeyReveal

// UnknownOddTypes is a map of unknown odd types that were encountered
// during decoding. This map is used to preserve unknown types that we
Expand Down
24 changes: 12 additions & 12 deletions proof/proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ func TestProofEncoding(t *testing.T) {
require.False(t, IsSingleProof(nil))

// Test with a nil tapscript root in the group reveal.
proof.GroupKeyReveal.TapscriptRoot = nil
proof.GroupKeyReveal.SetTapscriptRoot(nil)
file, err = NewFile(V0, proof, proof)
require.NoError(t, err)
proof.AdditionalInputs = []File{*file, *file}
Expand Down Expand Up @@ -262,12 +262,10 @@ func genRandomGenesisWithProof(t testing.TB, assetType asset.Type,
asset.WithAssetVersion(assetVersion),
)
assetGroupKey := asset.RandGroupKey(t, assetGenesis, protoAsset)
groupKeyReveal := &asset.GroupKeyReveal{
RawKey: asset.ToSerialized(
assetGroupKey.RawKey.PubKey,
),
TapscriptRoot: assetGroupKey.TapscriptRoot,
}
groupKeyReveal := asset.NewGroupKeyRevealV0(
asset.ToSerialized(assetGroupKey.RawKey.PubKey),
assetGroupKey.TapscriptRoot,
)

if groupRevealMutator != nil {
groupRevealMutator(groupKeyReveal)
Expand Down Expand Up @@ -362,7 +360,7 @@ func genRandomGenesisWithProof(t testing.TB, assetType asset.Type,

type genMutator func(*asset.Genesis)

type groupRevealMutator func(*asset.GroupKeyReveal)
type groupRevealMutator func(asset.GroupKeyReveal)

type genRevealMutator func(*asset.Genesis) *asset.Genesis

Expand Down Expand Up @@ -557,8 +555,10 @@ func TestGenesisProofVerification(t *testing.T) {
name: "group key reveal invalid key",
assetType: asset.Collectible,
noMetaHash: true,
groupRevealMutator: func(gkr *asset.GroupKeyReveal) {
gkr.RawKey[0] = 0x01
groupRevealMutator: func(gkr asset.GroupKeyReveal) {
rawKey := gkr.RawKey()
rawKey[0] = 0x01
gkr.SetRawKey(rawKey)
},
expectedErr: secp256k1.ErrPubKeyInvalidFormat,
},
Expand All @@ -567,8 +567,8 @@ func TestGenesisProofVerification(t *testing.T) {
assetType: asset.Normal,
amount: &amount,
noMetaHash: true,
groupRevealMutator: func(gkr *asset.GroupKeyReveal) {
gkr.TapscriptRoot = test.RandBytes(32)
groupRevealMutator: func(gkr asset.GroupKeyReveal) {
gkr.SetTapscriptRoot(test.RandBytes(32))
},
expectedErr: ErrGroupKeyRevealMismatch,
},
Expand Down
4 changes: 2 additions & 2 deletions proof/records.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,14 +378,14 @@ func GenesisRevealRecord(genesis **asset.Genesis) tlv.Record {
)
}

func GroupKeyRevealRecord(reveal **asset.GroupKeyReveal) tlv.Record {
func GroupKeyRevealRecord(reveal *asset.GroupKeyReveal) tlv.Record {
recordSize := func() uint64 {
if reveal == nil || *reveal == nil {
return 0
}
r := *reveal
return uint64(
btcec.PubKeyBytesLenCompressed + len(r.TapscriptRoot),
btcec.PubKeyBytesLenCompressed + len(r.TapscriptRoot()),
)
}
return tlv.MakeDynamicRecord(
Expand Down
5 changes: 3 additions & 2 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -1818,9 +1818,10 @@ func (r *rpcServer) marshalProof(ctx context.Context, p *proof.Proof,

var GroupKeyReveal taprpc.GroupKeyReveal
if rpcGroupKey != nil {
rawKey := rpcGroupKey.RawKey()
GroupKeyReveal = taprpc.GroupKeyReveal{
RawGroupKey: rpcGroupKey.RawKey[:],
TapscriptRoot: rpcGroupKey.TapscriptRoot,
RawGroupKey: rawKey[:],
TapscriptRoot: rpcGroupKey.TapscriptRoot(),
}
}

Expand Down
Loading

0 comments on commit 3d3b254

Please sign in to comment.