Skip to content

Commit

Permalink
Merge pull request #827 from lightninglabs/mint_early_sprouting
Browse files Browse the repository at this point in the history
tapgarden: add FundBatch and SealBatch requests
  • Loading branch information
jharveyb authored Apr 9, 2024
2 parents 39bbc18 + 132a2d0 commit dc8932d
Show file tree
Hide file tree
Showing 25 changed files with 2,179 additions and 911 deletions.
195 changes: 118 additions & 77 deletions asset/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,30 @@ type GroupKeyRequest struct {
NewAsset *Asset
}

// GroupVirtualTx contains all the information needed to produce an asset group
// witness, except for the group internal key descriptor (or private key). A
// GroupVirtualTx is constructed from a GroupKeyRequest.
type GroupVirtualTx struct {
// Tx is a virtual transaction that represents the genesis state
// transition of a grouped asset.
Tx wire.MsgTx

// PrevOut is a transaction output that represents a grouped asset.
// PrevOut uses the tweaked group key as its PkScript. This is used in
// combination with GroupVirtualTx.Tx as input for a GenesisSigner.
PrevOut wire.TxOut

// GenID is the asset ID of the grouped asset in a GroupKeyRequest. This
// ID is needed to construct a sign descriptor for a GenesisSigner, as
// it is the single tweak for the group internal key.
GenID ID

// TweakedKey is the tweaked group key for the given GroupKeyRequest.
// This is later used to construct a complete GroupKey, after a
// GenesisSigner has produced an asset group witness.
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)
Expand Down Expand Up @@ -959,6 +983,50 @@ func (s ScriptKey) IsUnSpendable() (bool, error) {
return NUMSPubKey.IsEqual(s.PubKey), nil
}

// IsEqual returns true is this tweaked script key is exactly equivalent to the
// passed other tweaked script key.
func (ts *TweakedScriptKey) IsEqual(other *TweakedScriptKey) bool {
if ts == nil {
return other == nil
}

if other == nil {
return false
}

if !bytes.Equal(ts.Tweak, other.Tweak) {
return false
}

return EqualKeyDescriptors(ts.RawKey, other.RawKey)
}

// IsEqual returns true is this script key is exactly equivalent to the passed
// other script key.
func (s *ScriptKey) IsEqual(otherScriptKey *ScriptKey) bool {
if s == nil {
return otherScriptKey == nil
}

if otherScriptKey == nil {
return false
}

if s.PubKey == nil {
return otherScriptKey.PubKey == nil
}

if otherScriptKey.PubKey == nil {
return false
}

if !s.TweakedScriptKey.IsEqual(otherScriptKey.TweakedScriptKey) {
return false
}

return s.PubKey.IsEqual(otherScriptKey.PubKey)
}

// NewScriptKey constructs a ScriptKey with only the publicly available
// information. This resulting key may or may not have a tweak applied to it.
func NewScriptKey(key *btcec.PublicKey) ScriptKey {
Expand Down Expand Up @@ -1045,14 +1113,20 @@ func (req *GroupKeyRequest) Validate() error {
return fmt.Errorf("missing group internal key")
}

tapscriptRootSize := len(req.TapscriptRoot)
if tapscriptRootSize != 0 && tapscriptRootSize != sha256.Size {
return fmt.Errorf("tapscript root must be %d bytes",
sha256.Size)
}

return nil
}

// DeriveGroupKey derives an asset's group key based on an internal public
// key descriptor, the original group asset genesis, and the asset's genesis.
func DeriveGroupKey(genSigner GenesisSigner, genBuilder GenesisTxBuilder,
req GroupKeyRequest) (*GroupKey, error) {

// BuildGroupVirtualTx derives the tweaked group key for group key request,
// and constructs the group virtual TX needed to construct a sign descriptor and
// produce an asset group witness.
func (req *GroupKeyRequest) BuildGroupVirtualTx(genBuilder GenesisTxBuilder) (
*GroupVirtualTx, error) {
// First, perform the final checks on the asset being authorized for
// group membership.
err := req.Validate()
Expand All @@ -1064,7 +1138,7 @@ func DeriveGroupKey(genSigner GenesisSigner, genBuilder GenesisTxBuilder,
// creating the virtual minting transaction.
genesisTweak := req.AnchorGen.ID()
tweakedGroupKey, err := GroupPubKey(
req.RawKey.PubKey, genesisTweak[:], nil,
req.RawKey.PubKey, genesisTweak[:], req.TapscriptRoot,
)
if err != nil {
return nil, fmt.Errorf("cannot tweak group key: %w", err)
Expand All @@ -1082,91 +1156,57 @@ func DeriveGroupKey(genSigner GenesisSigner, genBuilder GenesisTxBuilder,
return nil, fmt.Errorf("cannot build virtual tx: %w", err)
}

// Build the static signing descriptor needed to sign the virtual
// minting transaction. This is restricted to group keys with an empty
// tapscript root and key path spends.
signDesc := &lndclient.SignDescriptor{
KeyDesc: req.RawKey,
SingleTweak: genesisTweak[:],
SignMethod: input.TaprootKeySpendBIP0086SignMethod,
Output: prevOut,
HashType: txscript.SigHashDefault,
InputIndex: 0,
}
sig, err := genSigner.SignVirtualTx(signDesc, genesisTx, prevOut)
if err != nil {
return nil, err
}

return &GroupKey{
RawKey: req.RawKey,
GroupPubKey: *tweakedGroupKey,
Witness: wire.TxWitness{sig.Serialize()},
return &GroupVirtualTx{
Tx: *genesisTx,
PrevOut: *prevOut,
GenID: genesisTweak,
TweakedKey: *tweakedGroupKey,
}, nil
}

// DeriveCustomGroupKey derives an asset's group key based on a signing
// descriptor, the original group asset genesis, and the asset's genesis.
func DeriveCustomGroupKey(genSigner GenesisSigner, genBuilder GenesisTxBuilder,
req GroupKeyRequest, tapLeaf *psbt.TaprootTapLeafScript,
scriptWitness []byte) (*GroupKey, error) {
// AssembleGroupKeyFromWitness constructs a group key given a group witness
// generated externally.
func AssembleGroupKeyFromWitness(genTx GroupVirtualTx, req GroupKeyRequest,
tapLeaf *psbt.TaprootTapLeafScript, scriptWitness []byte) (*GroupKey,
error) {

// First, perform the final checks on the asset being authorized for
// group membership.
err := req.Validate()
if err != nil {
return nil, err
if scriptWitness == nil {
return nil, fmt.Errorf("script witness cannot be nil")
}

// Compute the tweaked group key and set it in the asset before
// creating the virtual minting transaction.
genesisTweak := req.AnchorGen.ID()
tweakedGroupKey, err := GroupPubKey(
req.RawKey.PubKey, genesisTweak[:], req.TapscriptRoot,
)
if err != nil {
return nil, fmt.Errorf("cannot tweak group key: %w", err)
groupKey := &GroupKey{
RawKey: req.RawKey,
GroupPubKey: genTx.TweakedKey,
TapscriptRoot: req.TapscriptRoot,
Witness: wire.TxWitness{scriptWitness},
}

assetWithGroup := req.NewAsset.Copy()
assetWithGroup.GroupKey = &GroupKey{
GroupPubKey: *tweakedGroupKey,
}

// Exit early if a group witness is already given, since we don't need
// to construct a virtual TX nor produce a signature.
if scriptWitness != nil {
if tapLeaf == nil {
return nil, fmt.Errorf("need tap leaf with group " +
"script witness")
if tapLeaf != nil {
if tapLeaf.LeafVersion != txscript.BaseLeafVersion {
return nil, fmt.Errorf("unsupported script version")
}

witness := wire.TxWitness{
scriptWitness, tapLeaf.Script, tapLeaf.ControlBlock,
}

return &GroupKey{
RawKey: req.RawKey,
GroupPubKey: *tweakedGroupKey,
TapscriptRoot: req.TapscriptRoot,
Witness: witness,
}, nil
groupKey.Witness = append(
groupKey.Witness, tapLeaf.Script, tapLeaf.ControlBlock,
)
}

// Build the virtual transaction that represents the minting of the new
// asset, which will be signed to generate the group witness.
genesisTx, prevOut, err := genBuilder.BuildGenesisTx(assetWithGroup)
if err != nil {
return nil, fmt.Errorf("cannot build virtual tx: %w", err)
}
return groupKey, nil
}

// DeriveGroupKey derives an asset's group key based on an internal public key
// descriptor, the original group asset genesis, and the asset's genesis.
func DeriveGroupKey(genSigner GenesisSigner, genTx GroupVirtualTx,
req GroupKeyRequest, tapLeaf *psbt.TaprootTapLeafScript) (*GroupKey,
error) {

// Populate the signing descriptor needed to sign the virtual minting
// transaction.
signDesc := &lndclient.SignDescriptor{
KeyDesc: req.RawKey,
SingleTweak: genesisTweak[:],
SingleTweak: genTx.GenID[:],
TapTweak: req.TapscriptRoot,
Output: prevOut,
Output: &genTx.PrevOut,
HashType: txscript.SigHashDefault,
InputIndex: 0,
}
Expand All @@ -1193,7 +1233,7 @@ func DeriveCustomGroupKey(genSigner GenesisSigner, genBuilder GenesisTxBuilder,
return nil, fmt.Errorf("bad sign descriptor for group key")
}

sig, err := genSigner.SignVirtualTx(signDesc, genesisTx, prevOut)
sig, err := genSigner.SignVirtualTx(signDesc, &genTx.Tx, &genTx.PrevOut)
if err != nil {
return nil, err
}
Expand All @@ -1204,13 +1244,14 @@ func DeriveCustomGroupKey(genSigner GenesisSigner, genBuilder GenesisTxBuilder,
// the control block to the witness, otherwise the verifier will reject
// the generated witness.
if signDesc.SignMethod == input.TaprootScriptSpendSignMethod {
witness = append(witness, signDesc.WitnessScript)
witness = append(witness, tapLeaf.ControlBlock)
witness = append(
witness, signDesc.WitnessScript, tapLeaf.ControlBlock,
)
}

return &GroupKey{
RawKey: signDesc.KeyDesc,
GroupPubKey: *tweakedGroupKey,
GroupPubKey: genTx.TweakedKey,
TapscriptRoot: signDesc.TapTweak,
Witness: witness,
}, nil
Expand Down
34 changes: 24 additions & 10 deletions asset/asset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -800,7 +800,10 @@ func TestAssetGroupKey(t *testing.T) {
// need to provide a copy to arrive at the same result.
protoAsset := NewAssetNoErr(t, g, 1, 0, 0, fakeScriptKey, nil)
groupReq := NewGroupKeyRequestNoErr(t, fakeKeyDesc, g, protoAsset, nil)
keyGroup, err := DeriveGroupKey(genSigner, &genBuilder, *groupReq)
genTx, err := groupReq.BuildGroupVirtualTx(&genBuilder)
require.NoError(t, err)

keyGroup, err := DeriveGroupKey(genSigner, *genTx, *groupReq, nil)
require.NoError(t, err)

require.Equal(
Expand All @@ -816,9 +819,10 @@ func TestAssetGroupKey(t *testing.T) {
groupReq = NewGroupKeyRequestNoErr(
t, test.PubToKeyDesc(privKey.PubKey()), g, protoAsset, tapTweak,
)
keyGroup, err = DeriveCustomGroupKey(
genSigner, &genBuilder, *groupReq, nil, nil,
)
genTx, err = groupReq.BuildGroupVirtualTx(&genBuilder)
require.NoError(t, err)

keyGroup, err = DeriveGroupKey(genSigner, *genTx, *groupReq, nil)
require.NoError(t, err)

require.Equal(
Expand Down Expand Up @@ -873,34 +877,44 @@ func TestDeriveGroupKey(t *testing.T) {
}

// A prototype asset is required for building the genesis virtual TX.
_, err := DeriveGroupKey(genSigner, &genBuilder, groupReq)
_, err := groupReq.BuildGroupVirtualTx(&genBuilder)
require.ErrorContains(t, err, "grouped asset cannot be nil")

// The prototype asset must have a genesis witness.
groupReq.NewAsset = nonGenProtoAsset
_, err = DeriveGroupKey(genSigner, &genBuilder, groupReq)
_, err = groupReq.BuildGroupVirtualTx(&genBuilder)
require.ErrorContains(t, err, "asset is not a genesis asset")

// The prototype asset must not have a group key set.
groupReq.NewAsset = groupedProtoAsset
_, err = DeriveGroupKey(genSigner, &genBuilder, groupReq)
_, err = groupReq.BuildGroupVirtualTx(&genBuilder)
require.ErrorContains(t, err, "asset already has group key")

// The anchor genesis used for signing must have the same asset type
// as the prototype asset being signed.
groupReq.AnchorGen = collectGen
groupReq.NewAsset = protoAsset
_, err = DeriveGroupKey(genSigner, &genBuilder, groupReq)
_, err = groupReq.BuildGroupVirtualTx(&genBuilder)
require.ErrorContains(t, err, "asset group type mismatch")

// The group key request must include an internal key.
groupReq.AnchorGen = baseGen
groupReq.RawKey.PubKey = nil
_, err = DeriveGroupKey(genSigner, &genBuilder, groupReq)
_, err = groupReq.BuildGroupVirtualTx(&genBuilder)
require.ErrorContains(t, err, "missing group internal key")

// The tapscript root in the group key request must be exactly 32 bytes
// if present.
groupReq.RawKey = groupKeyDesc
groupKey, err := DeriveGroupKey(genSigner, &genBuilder, groupReq)
groupReq.TapscriptRoot = test.RandBytes(33)
_, err = groupReq.BuildGroupVirtualTx(&genBuilder)
require.ErrorContains(t, err, "tapscript root must be 32 bytes")

groupReq.TapscriptRoot = test.RandBytes(32)
genTx, err := groupReq.BuildGroupVirtualTx(&genBuilder)
require.NoError(t, err)

groupKey, err := DeriveGroupKey(genSigner, *genTx, groupReq, nil)
require.NoError(t, err)
require.NotNil(t, groupKey)
}
Expand Down
Loading

0 comments on commit dc8932d

Please sign in to comment.