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

[tapdb]: add script key type enum, run Golang based post migration checks #1198

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
19 changes: 14 additions & 5 deletions address/book.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ type Storage interface {
// being relevant for the local wallet (e.g. show assets received on
// this key in the asset list and balances).
InsertScriptKey(ctx context.Context, scriptKey asset.ScriptKey,
declareAsKnown bool) error
declareAsKnown bool, keyType asset.ScriptKeyType) error
}

// KeyRing is used to create script and internal keys for Taproot Asset
Expand Down Expand Up @@ -337,7 +337,12 @@ func (b *Book) NewAddressWithKeys(ctx context.Context, addrVersion Version,
if err != nil {
return nil, fmt.Errorf("unable to insert internal key: %w", err)
}
err = b.cfg.Store.InsertScriptKey(ctx, scriptKey, true)

// We might not know the type of script key, if it was given to us
// through an RPC call. So we make a guess here.
keyType := scriptKey.GuessType()

err = b.cfg.Store.InsertScriptKey(ctx, scriptKey, true, keyType)
if err != nil {
return nil, fmt.Errorf("unable to insert script key: %w", err)
}
Expand Down Expand Up @@ -374,9 +379,11 @@ func (b *Book) IsLocalKey(ctx context.Context,

// InsertScriptKey inserts an address related script key into the database.
func (b *Book) InsertScriptKey(ctx context.Context, scriptKey asset.ScriptKey,
declareAsKnown bool) error {
declareAsKnown bool, keyType asset.ScriptKeyType) error {

return b.cfg.Store.InsertScriptKey(ctx, scriptKey, declareAsKnown)
return b.cfg.Store.InsertScriptKey(
ctx, scriptKey, declareAsKnown, keyType,
)
}

// NextInternalKey derives then inserts an internal key into the database to
Expand Down Expand Up @@ -411,7 +418,9 @@ func (b *Book) NextScriptKey(ctx context.Context,
}

scriptKey := asset.NewScriptKeyBip86(keyDesc)
err = b.cfg.Store.InsertScriptKey(ctx, scriptKey, true)
err = b.cfg.Store.InsertScriptKey(
ctx, scriptKey, true, asset.ScriptKeyBip86,
)
if err != nil {
return asset.ScriptKey{}, err
}
Expand Down
80 changes: 74 additions & 6 deletions asset/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,43 @@ const (
EncodeSegwit
)

// ScriptKeyType denotes the type of script key used for an asset. This type is
// serialized to the database, so we don't use iota for the values to ensure
// they don't change by accident.
type ScriptKeyType uint8

const (
// ScriptKeyUnknown is the default script key type used for assets that
// we don't know the type of. This should only be stored for assets
// where we don't know the internal key of the script key (e.g. for
// imported proofs).
ScriptKeyUnknown ScriptKeyType = 0

// ScriptKeyBip86 is the script key type used for assets that use the
// BIP86 style tweak (e.g. an empty tweak).
ScriptKeyBip86 ScriptKeyType = 1

// ScriptKeyScriptPathExternal is the script key type used for assets
// that use a script path that is defined by an external application.
// Keys with script paths are normally not shown in asset balances and
// by default aren't used for coin selection unless specifically
// requested.
ScriptKeyScriptPathExternal ScriptKeyType = 2

// ScriptKeyBurn is the script key type used for assets that are burned
// and not spendable.
ScriptKeyBurn ScriptKeyType = 3

// ScriptKeyScriptPathChannel is the script key type used for assets
// that use a script path that is somehow related to Taproot Asset
// Channels. That means the script key is either a funding key
// (OP_TRUE), a commitment output key (to_local, to_remote, htlc), or a
// HTLC second-level transaction output key.
// Keys related to channels are not shown in asset balances (unless
// specifically requested) and _never_ used for coin selection.
ScriptKeyScriptPathChannel ScriptKeyType = 4
)

var (
// ZeroPrevID is the blank prev ID used for genesis assets and also
// asset split leaves.
Expand Down Expand Up @@ -1176,6 +1213,9 @@ type TweakedScriptKey struct {
// flag has is that assets with a declared key are shown in the asset
// list/balance.
DeclaredKnown bool

// Type is the type of script key that is being used.
Type ScriptKeyType
}

// IsEqual returns true is this tweaked script key is exactly equivalent to the
Expand Down Expand Up @@ -1261,15 +1301,44 @@ func (s *ScriptKey) HasScriptPath() bool {
return s.TweakedScriptKey != nil && len(s.TweakedScriptKey.Tweak) > 0
}

// GuessType tries to guess the type of the script key based on the information
// available.
func (s *ScriptKey) GuessType() ScriptKeyType {
// If we have an explicit script key type set, we can return that.
if s.TweakedScriptKey != nil &&
s.TweakedScriptKey.Type != ScriptKeyUnknown {

return s.TweakedScriptKey.Type
}

// If there is a known tweak, then we know that this is a script path
// key. We never return the channel type, since those keys should always
// be declared properly, and we never should need to guess their type.
if s.HasScriptPath() {
return ScriptKeyScriptPathExternal
}

// Do we know the internal key? Then we can check whether it is a
// BIP-0086 key.
if s.PubKey != nil && s.TweakedScriptKey != nil &&
s.TweakedScriptKey.RawKey.PubKey != nil {

bip86 := NewScriptKeyBip86(s.TweakedScriptKey.RawKey)
if bip86.PubKey.IsEqual(s.PubKey) {
return ScriptKeyBip86
}
}

return ScriptKeyUnknown
}

// 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 {
// Since we'll never query lnd for a tweaked key, it doesn't matter if
// we lose the parity information here. And this will only ever be
// serialized on chain in a 32-bit representation as well.
key, _ = schnorr.ParsePubKey(
schnorr.SerializePubKey(key),
)
key, _ = schnorr.ParsePubKey(schnorr.SerializePubKey(key))
return ScriptKey{
PubKey: key,
}
Expand All @@ -1281,9 +1350,7 @@ func NewScriptKey(key *btcec.PublicKey) ScriptKey {
func NewScriptKeyBip86(rawKey keychain.KeyDescriptor) ScriptKey {
// Tweak the script key BIP-0086 style (such that we only commit to the
// internal key when signing).
tweakedPubKey := txscript.ComputeTaprootKeyNoScript(
rawKey.PubKey,
)
tweakedPubKey := txscript.ComputeTaprootKeyNoScript(rawKey.PubKey)

// Since we'll never query lnd for a tweaked key, it doesn't matter if
// we lose the parity information here. And this will only ever be
Expand All @@ -1296,6 +1363,7 @@ func NewScriptKeyBip86(rawKey keychain.KeyDescriptor) ScriptKey {
PubKey: tweakedPubKey,
TweakedScriptKey: &TweakedScriptKey{
RawKey: rawKey,
Type: ScriptKeyBip86,
},
}
}
Expand Down
7 changes: 6 additions & 1 deletion rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -7322,7 +7322,12 @@ func (r *rpcServer) DeclareScriptKey(ctx context.Context,
err)
}

err = r.cfg.TapAddrBook.InsertScriptKey(ctx, *scriptKey, true)
// Because we've been given the key over the RPC interface, we can't be
// 100% sure of the type. But we can make a best effort guess based on
// the fields the user has set.
keyType := scriptKey.GuessType()

err = r.cfg.TapAddrBook.InsertScriptKey(ctx, *scriptKey, true, keyType)
if err != nil {
return nil, fmt.Errorf("error inserting script key: %w", err)
}
Expand Down
9 changes: 1 addition & 8 deletions tapcfg/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,6 @@ import (
"github.com/lightningnetwork/lnd/signal"
)

// databaseBackend is an interface that contains all methods our different
// database backends implement.
type databaseBackend interface {
tapdb.BatchedQuerier
WithTx(tx *sql.Tx) *sqlc.Queries
}

// genServerConfig generates a server config from the given tapd config.
//
// NOTE: The RPCConfig and SignalInterceptor fields must be set by the caller
Expand All @@ -43,7 +36,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger,

var (
err error
db databaseBackend
db tapdb.DatabaseBackend
dbType sqlc.BackendType
)

Expand Down
4 changes: 3 additions & 1 deletion tapchannel/aux_funding_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,9 @@ func (f *FundingController) fundVirtualPacket(ctx context.Context,
// We'll also need to import the funding script key into the wallet so
// the asset will be materialized in the asset table and show up in the
// balance correctly.
err := f.cfg.AddrBook.InsertScriptKey(ctx, fundingScriptKey, true)
err := f.cfg.AddrBook.InsertScriptKey(
ctx, fundingScriptKey, true, asset.ScriptKeyScriptPathChannel,
)
if err != nil {
return nil, fmt.Errorf("unable to insert script key: %w", err)
}
Expand Down
8 changes: 6 additions & 2 deletions tapchannel/aux_sweeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,9 @@ func (a *AuxSweeper) importCommitScriptKeys(req lnwallet.ResolutionReq) error {

ctxb := context.Background()
for _, key := range keysToImport {
err := a.cfg.AddrBook.InsertScriptKey(ctxb, key, true)
err := a.cfg.AddrBook.InsertScriptKey(
ctxb, key, true, asset.ScriptKeyScriptPathChannel,
)
if err != nil {
return fmt.Errorf("unable to insert script "+
"key: %w", err)
Expand Down Expand Up @@ -938,7 +940,9 @@ func (a *AuxSweeper) importCommitTx(req lnwallet.ResolutionReq,
// the asset will be materialized in the asset table and show up in the
// balance correctly.
ctxb := context.Background()
err := a.cfg.AddrBook.InsertScriptKey(ctxb, fundingScriptKey, true)
err := a.cfg.AddrBook.InsertScriptKey(
ctxb, fundingScriptKey, true, asset.ScriptKeyScriptPathChannel,
)
if err != nil {
return fmt.Errorf("unable to insert script key: %w", err)
}
Expand Down
68 changes: 15 additions & 53 deletions tapdb/addrs.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,22 +393,13 @@ func (t *TapAddressBook) QueryAddrs(ctx context.Context,
}
}

rawScriptKey, err := btcec.ParsePubKey(
addr.RawScriptKey,
scriptKey, err := parseScriptKey(
addr.InternalKey, addr.ScriptKey,
)
if err != nil {
return fmt.Errorf("unable to decode "+
"script key: %w", err)
}
rawScriptKeyDesc := keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(
addr.ScriptKeyFamily,
),
Index: uint32(addr.ScriptKeyIndex),
},
PubKey: rawScriptKey,
}

internalKey, err := btcec.ParsePubKey(addr.RawTaprootKey)
if err != nil {
Expand All @@ -425,11 +416,6 @@ func (t *TapAddressBook) QueryAddrs(ctx context.Context,
PubKey: internalKey,
}

scriptKey, err := btcec.ParsePubKey(addr.TweakedScriptKey)
if err != nil {
return err
}

taprootOutputKey, err := schnorr.ParsePubKey(
addr.TaprootOutputKey,
)
Expand All @@ -456,8 +442,8 @@ func (t *TapAddressBook) QueryAddrs(ctx context.Context,

tapAddr, err := address.New(
address.Version(addr.Version), assetGenesis,
groupKey, groupWitness,
*scriptKey, *internalKey, uint64(addr.Amount),
groupKey, groupWitness, *scriptKey.PubKey,
*internalKey, uint64(addr.Amount),
tapscriptSibling, t.params, *proofCourierAddr,
address.WithAssetVersion(
asset.Version(addr.AssetVersion),
Expand All @@ -467,16 +453,9 @@ func (t *TapAddressBook) QueryAddrs(ctx context.Context,
return fmt.Errorf("unable to make addr: %w", err)
}

declaredKnown := extractBool(
addr.ScriptKeyDeclaredKnown,
)
addrs = append(addrs, address.AddrWithKeyInfo{
Tap: tapAddr,
ScriptKeyTweak: asset.TweakedScriptKey{
RawKey: rawScriptKeyDesc,
Tweak: addr.ScriptKeyTweak,
DeclaredKnown: declaredKnown,
},
Tap: tapAddr,
ScriptKeyTweak: *scriptKey.TweakedScriptKey,
InternalKeyDesc: internalKeyDesc,
TaprootOutputKey: *taprootOutputKey,
CreationTime: addr.CreationTime.UTC(),
Expand Down Expand Up @@ -560,21 +539,7 @@ func fetchAddr(ctx context.Context, db AddrBook, params *address.ChainParams,
}
}

rawScriptKey, err := btcec.ParsePubKey(dbAddr.RawScriptKey)
if err != nil {
return nil, fmt.Errorf("unable to decode script key: %w", err)
}
scriptKeyDesc := keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(
dbAddr.ScriptKeyFamily,
),
Index: uint32(dbAddr.ScriptKeyIndex),
},
PubKey: rawScriptKey,
}

scriptKey, err := btcec.ParsePubKey(dbAddr.TweakedScriptKey)
scriptKey, err := parseScriptKey(dbAddr.InternalKey, dbAddr.ScriptKey)
if err != nil {
return nil, fmt.Errorf("unable to decode script key: %w", err)
}
Expand Down Expand Up @@ -611,23 +576,18 @@ func fetchAddr(ctx context.Context, db AddrBook, params *address.ChainParams,

tapAddr, err := address.New(
address.Version(dbAddr.Version), genesis, groupKey,
groupWitness, *scriptKey, *internalKey, uint64(dbAddr.Amount),
tapscriptSibling, params, *proofCourierAddr,
groupWitness, *scriptKey.PubKey, *internalKey,
uint64(dbAddr.Amount), tapscriptSibling, params,
*proofCourierAddr,
address.WithAssetVersion(asset.Version(dbAddr.AssetVersion)),
)
if err != nil {
return nil, fmt.Errorf("unable to make addr: %w", err)
}

return &address.AddrWithKeyInfo{
Tap: tapAddr,
ScriptKeyTweak: asset.TweakedScriptKey{
RawKey: scriptKeyDesc,
Tweak: dbAddr.ScriptKeyTweak,
DeclaredKnown: extractBool(
dbAddr.ScriptKeyDeclaredKnown,
),
},
Tap: tapAddr,
ScriptKeyTweak: *scriptKey.TweakedScriptKey,
InternalKeyDesc: internalKeyDesc,
TaprootOutputKey: *taprootOutputKey,
CreationTime: dbAddr.CreationTime.UTC(),
Expand Down Expand Up @@ -678,7 +638,8 @@ func (t *TapAddressBook) InsertInternalKey(ctx context.Context,
// it can be recognized as belonging to the wallet when a transfer comes in
// later on.
func (t *TapAddressBook) InsertScriptKey(ctx context.Context,
scriptKey asset.ScriptKey, declaredKnown bool) error {
scriptKey asset.ScriptKey, declaredKnown bool,
keyType asset.ScriptKeyType) error {

var writeTxOpts AddrBookTxOptions
return t.db.ExecTx(ctx, &writeTxOpts, func(q AddrBook) error {
Expand All @@ -695,6 +656,7 @@ func (t *TapAddressBook) InsertScriptKey(ctx context.Context,
TweakedScriptKey: scriptKey.PubKey.SerializeCompressed(),
Tweak: scriptKey.Tweak,
DeclaredKnown: sqlBool(declaredKnown),
KeyType: sqlInt16(keyType),
})
return err
})
Expand Down
4 changes: 2 additions & 2 deletions tapdb/addrs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -663,8 +663,8 @@
// insertScriptKeyWithNull is a helper function that inserts a script key with a
// a NULL value for declared known. We use this so we can insert a NULL vs an
// actual value. It is identical to the InsertScriptKey.
func insertScriptKeyWithNull(ctx context.Context, key asset.ScriptKey,
) func(AddrBook) error {
func insertScriptKeyWithNull(ctx context.Context,
key asset.ScriptKey) func(AddrBook) error {

return func(q AddrBook) error {
internalKeyID, err := insertInternalKey(
Expand Down Expand Up @@ -714,7 +714,7 @@

// We'll insert a random script key into the database. We won't
// declare it as known though.
err := addrBook.InsertScriptKey(ctx, scriptKey, known)

Check failure on line 717 in tapdb/addrs_test.go

View workflow job for this annotation

GitHub Actions / run unit tests (unit-race)

not enough arguments in call to addrBook.InsertScriptKey

Check failure on line 717 in tapdb/addrs_test.go

View workflow job for this annotation

GitHub Actions / run unit tests (unit-cover)

not enough arguments in call to addrBook.InsertScriptKey

Check failure on line 717 in tapdb/addrs_test.go

View workflow job for this annotation

GitHub Actions / run unit tests (unit dbbackend=postgres)

not enough arguments in call to addrBook.InsertScriptKey
require.NoError(t, err)

// We'll fetch the script key and confirm that it's not known.
Expand All @@ -723,7 +723,7 @@
known = true

// We'll now insert it again, but this time declare it as known.
err = addrBook.InsertScriptKey(ctx, scriptKey, known)

Check failure on line 726 in tapdb/addrs_test.go

View workflow job for this annotation

GitHub Actions / run unit tests (unit-race)

not enough arguments in call to addrBook.InsertScriptKey

Check failure on line 726 in tapdb/addrs_test.go

View workflow job for this annotation

GitHub Actions / run unit tests (unit-cover)

not enough arguments in call to addrBook.InsertScriptKey

Check failure on line 726 in tapdb/addrs_test.go

View workflow job for this annotation

GitHub Actions / run unit tests (unit dbbackend=postgres)

not enough arguments in call to addrBook.InsertScriptKey
require.NoError(t, err)

// We'll fetch the script key and confirm that it's known.
Expand All @@ -750,7 +750,7 @@
known = true

// We'll now insert it again, but this time declare it as known.
err = addrBook.InsertScriptKey(ctx, scriptKey, known)

Check failure on line 753 in tapdb/addrs_test.go

View workflow job for this annotation

GitHub Actions / run unit tests (unit-race)

not enough arguments in call to addrBook.InsertScriptKey

Check failure on line 753 in tapdb/addrs_test.go

View workflow job for this annotation

GitHub Actions / run unit tests (unit-cover)

not enough arguments in call to addrBook.InsertScriptKey

Check failure on line 753 in tapdb/addrs_test.go

View workflow job for this annotation

GitHub Actions / run unit tests (unit dbbackend=postgres)

not enough arguments in call to addrBook.InsertScriptKey
require.NoError(t, err)

// We'll fetch the script key and confirm that it's known.
Expand Down
Loading
Loading