Skip to content

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

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

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
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
21 changes: 11 additions & 10 deletions address/book.go
Original file line number Diff line number Diff line change
@@ -134,13 +134,9 @@ type Storage interface {

// InsertScriptKey inserts an address related script key into the
// database, so it can be recognized as belonging to the wallet when a
// transfer comes in later on. The script key can be declared as known
// if it contains an internal key that isn't derived by the backing
// wallet (e.g. NUMS key) but it should still be recognized as a key
// being relevant for the local wallet (e.g. show assets received on
// this key in the asset list and balances).
// transfer comes in later on.
InsertScriptKey(ctx context.Context, scriptKey asset.ScriptKey,
declareAsKnown bool) error
keyType asset.ScriptKeyType) error
}

// KeyRing is used to create script and internal keys for Taproot Asset
@@ -401,7 +397,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, keyType)
if err != nil {
return nil, fmt.Errorf("unable to insert script key: %w", err)
}
@@ -438,9 +439,9 @@ 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 {
keyType asset.ScriptKeyType) error {

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

// NextInternalKey derives then inserts an internal key into the database to
@@ -475,7 +476,7 @@ 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, asset.ScriptKeyBip86)
if err != nil {
return asset.ScriptKey{}, err
}
108 changes: 88 additions & 20 deletions asset/asset.go
Original file line number Diff line number Diff line change
@@ -108,6 +108,50 @@ 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

// ScriptKeyTombstone is the script key type used for assets that are
// not spendable and have been marked as tombstones. This is only the
// case for zero-value assets that result from a non-interactive (TAP
// address) send where no change was left over. The script key used for
// this is a NUMS key that is not spendable.
ScriptKeyTombstone ScriptKeyType = 4

// 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 are _never_ used for coin selection.
ScriptKeyScriptPathChannel ScriptKeyType = 5
)

var (
// ZeroPrevID is the blank prev ID used for genesis assets and also
// asset split leaves.
@@ -993,14 +1037,8 @@ type TweakedScriptKey struct {
// public key. If this is nil, then a BIP-0086 tweak is assumed.
Tweak []byte

// DeclaredKnown indicates that this script key has been explicitly
// declared as being important to the local wallet, even if it might not
// be fully known to the local wallet. This could perhaps also be named
// "imported", though that might imply that the corresponding private
// key was also somehow imported and available. The only relevance this
// 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
@@ -1071,12 +1109,9 @@ func (s *ScriptKey) IsEqual(otherScriptKey *ScriptKey) bool {
// the local wallet or was explicitly declared to be known by using the
// DeclareScriptKey RPC. Knowing the key conceptually means the key belongs to
// the local wallet or is at least known by a software that operates on the
// local wallet. The DeclaredAsKnown flag is never serialized in proofs, so this
// is never explicitly set for keys foreign to the local wallet. Therefore, if
// this method returns true for a script key, it means the asset with the script
// key will be shown in the wallet balance.
// local wallet.
func (s *ScriptKey) DeclaredAsKnown() bool {
return s.TweakedScriptKey != nil && s.TweakedScriptKey.DeclaredKnown
return s.TweakedScriptKey != nil && s.Type != ScriptKeyUnknown
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe ScriptKeyUnknown should be renamed to ScriptKeyTypeUnknown, because the type of script key is the thing that is unknown, even for a tracked script key. Have i understood that correctly?

}

// HasScriptPath returns true if we know the internals of the script key and
@@ -1086,15 +1121,49 @@ 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 {
Comment on lines +1124 to +1126
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "guess" is misleading here. This method appears to classify decisively whenever possible, but it never seems to make a prediction when uncertainty would be warranted. I suggest we rename to Type().

// 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
}

// Is it the known NUMS key? Then this is a tombstone output.
if s.PubKey != nil && s.PubKey.IsEqual(NUMSPubKey) {
return ScriptKeyTombstone
}

// 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,
}
@@ -1106,9 +1175,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
@@ -1121,6 +1188,7 @@ func NewScriptKeyBip86(rawKey keychain.KeyDescriptor) ScriptKey {
PubKey: tweakedPubKey,
TweakedScriptKey: &TweakedScriptKey{
RawKey: rawKey,
Type: ScriptKeyBip86,
},
}
}
@@ -1507,7 +1575,7 @@ func (a *Asset) Copy() *Asset {

if a.ScriptKey.TweakedScriptKey != nil {
assetCopy.ScriptKey.TweakedScriptKey = &TweakedScriptKey{
DeclaredKnown: a.ScriptKey.DeclaredKnown,
Type: a.ScriptKey.Type,
}
assetCopy.ScriptKey.RawKey = a.ScriptKey.RawKey

8 changes: 5 additions & 3 deletions asset/generators.go
Original file line number Diff line number Diff line change
@@ -71,9 +71,11 @@ var (
})
TweakedScriptKeyGen = rapid.Custom(func(t *rapid.T) TweakedScriptKey {
return TweakedScriptKey{
RawKey: KeyDescGen.Draw(t, "raw_key"),
Tweak: HashBytesGen.Draw(t, "tweak"),
DeclaredKnown: rapid.Bool().Draw(t, "declared_known"),
RawKey: KeyDescGen.Draw(t, "raw_key"),
Tweak: HashBytesGen.Draw(t, "tweak"),
Type: ScriptKeyType(
rapid.Int16().Draw(t, "script_key_type"),
),
}
})
ScriptKeyGen = rapid.Custom(func(t *rapid.T) ScriptKey {
30 changes: 15 additions & 15 deletions docs/examples/basic-price-oracle/go.mod
Original file line number Diff line number Diff line change
@@ -13,13 +13,13 @@ replace (
require (
github.com/lightninglabs/taproot-assets v0.5.0-rc1
github.com/sirupsen/logrus v1.9.3
google.golang.org/grpc v1.59.0
google.golang.org/grpc v1.64.1
)

require (
dario.cat/mergo v1.0.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/aead/siphash v1.0.1 // indirect
@@ -51,7 +51,7 @@ require (
github.com/decred/dcrd/lru v1.1.2 // indirect
github.com/docker/cli v28.0.1+incompatible // indirect
github.com/docker/docker v28.0.1+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fergusstrange/embedded-postgres v1.25.0 // indirect
@@ -71,7 +71,7 @@ require (
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
@@ -120,7 +120,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/opencontainers/runc v1.2.0 // indirect
github.com/ory/dockertest/v3 v3.10.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
@@ -151,31 +151,31 @@ require (
go.etcd.io/etcd/raft/v3 v3.5.12 // indirect
go.etcd.io/etcd/server/v3 v3.5.12 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.23.0 // indirect
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.36.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect
google.golang.org/protobuf v1.33.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.24.0 // indirect
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/errgo.v1 v1.0.1 // indirect
gopkg.in/macaroon-bakery.v2 v2.1.0 // indirect
gopkg.in/macaroon.v2 v2.1.0 // indirect
Loading
Loading