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

Refactor GroupKeyReveal for multi-version support #1235

Merged
merged 3 commits into from
Dec 5, 2024
Merged
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
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 @@ -1817,9 +1817,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
Loading