Skip to content

Commit 408c4a8

Browse files
committed
multi: add type to script key
With this commit we add a type enum to the script key. This will mostly be used on the database level to filter assets when listing or calculating balances. We can only be 100% certain about the type of a key in the case of BIP-0086 or where we explicitly create them in the tapchannel package. Anything else is a bit of a guess, since the keys are coming over the RPC interface. So we just assume any key that has a non-empty script path is an externally-defined (external app) script spend. This heuristic should be good enough to filter out channel related assets or assets that can't be spent without external instructions in balance RPC calls.
1 parent fbe456e commit 408c4a8

File tree

11 files changed

+236
-32
lines changed

11 files changed

+236
-32
lines changed

address/book.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ type Storage interface {
140140
// being relevant for the local wallet (e.g. show assets received on
141141
// this key in the asset list and balances).
142142
InsertScriptKey(ctx context.Context, scriptKey asset.ScriptKey,
143-
declareAsKnown bool) error
143+
declareAsKnown bool, keyType asset.ScriptKeyType) error
144144
}
145145

146146
// KeyRing is used to create script and internal keys for Taproot Asset
@@ -401,7 +401,12 @@ func (b *Book) NewAddressWithKeys(ctx context.Context, addrVersion Version,
401401
if err != nil {
402402
return nil, fmt.Errorf("unable to insert internal key: %w", err)
403403
}
404-
err = b.cfg.Store.InsertScriptKey(ctx, scriptKey, true)
404+
405+
// We might not know the type of script key, if it was given to us
406+
// through an RPC call. So we make a guess here.
407+
keyType := scriptKey.GuessType()
408+
409+
err = b.cfg.Store.InsertScriptKey(ctx, scriptKey, true, keyType)
405410
if err != nil {
406411
return nil, fmt.Errorf("unable to insert script key: %w", err)
407412
}
@@ -438,9 +443,11 @@ func (b *Book) IsLocalKey(ctx context.Context,
438443

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

443-
return b.cfg.Store.InsertScriptKey(ctx, scriptKey, declareAsKnown)
448+
return b.cfg.Store.InsertScriptKey(
449+
ctx, scriptKey, declareAsKnown, keyType,
450+
)
444451
}
445452

446453
// NextInternalKey derives then inserts an internal key into the database to
@@ -475,7 +482,9 @@ func (b *Book) NextScriptKey(ctx context.Context,
475482
}
476483

477484
scriptKey := asset.NewScriptKeyBip86(keyDesc)
478-
err = b.cfg.Store.InsertScriptKey(ctx, scriptKey, true)
485+
err = b.cfg.Store.InsertScriptKey(
486+
ctx, scriptKey, true, asset.ScriptKeyBip86,
487+
)
479488
if err != nil {
480489
return asset.ScriptKey{}, err
481490
}

asset/asset.go

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,50 @@ const (
108108
EncodeSegwit
109109
)
110110

111+
// ScriptKeyType denotes the type of script key used for an asset. This type is
112+
// serialized to the database, so we don't use iota for the values to ensure
113+
// they don't change by accident.
114+
type ScriptKeyType uint8
115+
116+
const (
117+
// ScriptKeyUnknown is the default script key type used for assets that
118+
// we don't know the type of. This should only be stored for assets
119+
// where we don't know the internal key of the script key (e.g. for
120+
// imported proofs).
121+
ScriptKeyUnknown ScriptKeyType = 0
122+
123+
// ScriptKeyBip86 is the script key type used for assets that use the
124+
// BIP86 style tweak (e.g. an empty tweak).
125+
ScriptKeyBip86 ScriptKeyType = 1
126+
127+
// ScriptKeyScriptPathExternal is the script key type used for assets
128+
// that use a script path that is defined by an external application.
129+
// Keys with script paths are normally not shown in asset balances and
130+
// by default aren't used for coin selection unless specifically
131+
// requested.
132+
ScriptKeyScriptPathExternal ScriptKeyType = 2
133+
134+
// ScriptKeyBurn is the script key type used for assets that are burned
135+
// and not spendable.
136+
ScriptKeyBurn ScriptKeyType = 3
137+
138+
// ScriptKeyTombstone is the script key type used for assets that are
139+
// not spendable and have been marked as tombstones. This is only the
140+
// case for zero-value assets that result from a non-interactive (TAP
141+
// address) send where no change was left over. The script key used for
142+
// this is a NUMS key that is not spendable.
143+
ScriptKeyTombstone ScriptKeyType = 4
144+
145+
// ScriptKeyScriptPathChannel is the script key type used for assets
146+
// that use a script path that is somehow related to Taproot Asset
147+
// Channels. That means the script key is either a funding key
148+
// (OP_TRUE), a commitment output key (to_local, to_remote, htlc), or a
149+
// HTLC second-level transaction output key.
150+
// Keys related to channels are not shown in asset balances (unless
151+
// specifically requested) and are _never_ used for coin selection.
152+
ScriptKeyScriptPathChannel ScriptKeyType = 5
153+
)
154+
111155
var (
112156
// ZeroPrevID is the blank prev ID used for genesis assets and also
113157
// asset split leaves.
@@ -984,6 +1028,9 @@ type TweakedScriptKey struct {
9841028
// flag has is that assets with a declared key are shown in the asset
9851029
// list/balance.
9861030
DeclaredKnown bool
1031+
1032+
// Type is the type of script key that is being used.
1033+
Type ScriptKeyType
9871034
}
9881035

9891036
// IsEqual returns true is this tweaked script key is exactly equivalent to the
@@ -1069,15 +1116,49 @@ func (s *ScriptKey) HasScriptPath() bool {
10691116
return s.TweakedScriptKey != nil && len(s.TweakedScriptKey.Tweak) > 0
10701117
}
10711118

1119+
// GuessType tries to guess the type of the script key based on the information
1120+
// available.
1121+
func (s *ScriptKey) GuessType() ScriptKeyType {
1122+
// If we have an explicit script key type set, we can return that.
1123+
if s.TweakedScriptKey != nil &&
1124+
s.TweakedScriptKey.Type != ScriptKeyUnknown {
1125+
1126+
return s.TweakedScriptKey.Type
1127+
}
1128+
1129+
// If there is a known tweak, then we know that this is a script path
1130+
// key. We never return the channel type, since those keys should always
1131+
// be declared properly, and we never should need to guess their type.
1132+
if s.HasScriptPath() {
1133+
return ScriptKeyScriptPathExternal
1134+
}
1135+
1136+
// Is it the known NUMS key? Then this is a tombstone output.
1137+
if s.PubKey != nil && s.PubKey.IsEqual(NUMSPubKey) {
1138+
return ScriptKeyTombstone
1139+
}
1140+
1141+
// Do we know the internal key? Then we can check whether it is a
1142+
// BIP-0086 key.
1143+
if s.PubKey != nil && s.TweakedScriptKey != nil &&
1144+
s.TweakedScriptKey.RawKey.PubKey != nil {
1145+
1146+
bip86 := NewScriptKeyBip86(s.TweakedScriptKey.RawKey)
1147+
if bip86.PubKey.IsEqual(s.PubKey) {
1148+
return ScriptKeyBip86
1149+
}
1150+
}
1151+
1152+
return ScriptKeyUnknown
1153+
}
1154+
10721155
// NewScriptKey constructs a ScriptKey with only the publicly available
10731156
// information. This resulting key may or may not have a tweak applied to it.
10741157
func NewScriptKey(key *btcec.PublicKey) ScriptKey {
10751158
// Since we'll never query lnd for a tweaked key, it doesn't matter if
10761159
// we lose the parity information here. And this will only ever be
10771160
// serialized on chain in a 32-bit representation as well.
1078-
key, _ = schnorr.ParsePubKey(
1079-
schnorr.SerializePubKey(key),
1080-
)
1161+
key, _ = schnorr.ParsePubKey(schnorr.SerializePubKey(key))
10811162
return ScriptKey{
10821163
PubKey: key,
10831164
}
@@ -1089,9 +1170,7 @@ func NewScriptKey(key *btcec.PublicKey) ScriptKey {
10891170
func NewScriptKeyBip86(rawKey keychain.KeyDescriptor) ScriptKey {
10901171
// Tweak the script key BIP-0086 style (such that we only commit to the
10911172
// internal key when signing).
1092-
tweakedPubKey := txscript.ComputeTaprootKeyNoScript(
1093-
rawKey.PubKey,
1094-
)
1173+
tweakedPubKey := txscript.ComputeTaprootKeyNoScript(rawKey.PubKey)
10951174

10961175
// Since we'll never query lnd for a tweaked key, it doesn't matter if
10971176
// we lose the parity information here. And this will only ever be
@@ -1104,6 +1183,7 @@ func NewScriptKeyBip86(rawKey keychain.KeyDescriptor) ScriptKey {
11041183
PubKey: tweakedPubKey,
11051184
TweakedScriptKey: &TweakedScriptKey{
11061185
RawKey: rawKey,
1186+
Type: ScriptKeyBip86,
11071187
},
11081188
}
11091189
}
@@ -1491,6 +1571,7 @@ func (a *Asset) Copy() *Asset {
14911571
if a.ScriptKey.TweakedScriptKey != nil {
14921572
assetCopy.ScriptKey.TweakedScriptKey = &TweakedScriptKey{
14931573
DeclaredKnown: a.ScriptKey.DeclaredKnown,
1574+
Type: a.ScriptKey.Type,
14941575
}
14951576
assetCopy.ScriptKey.RawKey = a.ScriptKey.RawKey
14961577

asset/generators.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ var (
7474
RawKey: KeyDescGen.Draw(t, "raw_key"),
7575
Tweak: HashBytesGen.Draw(t, "tweak"),
7676
DeclaredKnown: rapid.Bool().Draw(t, "declared_known"),
77+
Type: ScriptKeyType(
78+
rapid.Int16().Draw(t, "script_key_type"),
79+
),
7780
}
7881
})
7982
ScriptKeyGen = rapid.Custom(func(t *rapid.T) ScriptKey {

rpcserver.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7857,7 +7857,12 @@ func (r *rpcServer) DeclareScriptKey(ctx context.Context,
78577857
err)
78587858
}
78597859

7860-
err = r.cfg.TapAddrBook.InsertScriptKey(ctx, *scriptKey, true)
7860+
// Because we've been given the key over the RPC interface, we can't be
7861+
// 100% sure of the type. But we can make a best effort guess based on
7862+
// the fields the user has set.
7863+
keyType := scriptKey.GuessType()
7864+
7865+
err = r.cfg.TapAddrBook.InsertScriptKey(ctx, *scriptKey, true, keyType)
78617866
if err != nil {
78627867
return nil, fmt.Errorf("error inserting script key: %w", err)
78637868
}

tapchannel/aux_funding_controller.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -781,7 +781,9 @@ func (f *FundingController) fundVirtualPacket(ctx context.Context,
781781
// We'll also need to import the funding script key into the wallet so
782782
// the asset will be materialized in the asset table and show up in the
783783
// balance correctly.
784-
err := f.cfg.AddrBook.InsertScriptKey(ctx, fundingScriptKey, true)
784+
err := f.cfg.AddrBook.InsertScriptKey(
785+
ctx, fundingScriptKey, true, asset.ScriptKeyScriptPathChannel,
786+
)
785787
if err != nil {
786788
return nil, fmt.Errorf("unable to insert script key: %w", err)
787789
}

tapchannel/aux_sweeper.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1280,7 +1280,9 @@ func (a *AuxSweeper) importCommitScriptKeys(req lnwallet.ResolutionReq) error {
12801280

12811281
ctxb := context.Background()
12821282
for _, key := range keysToImport {
1283-
err := a.cfg.AddrBook.InsertScriptKey(ctxb, key, true)
1283+
err := a.cfg.AddrBook.InsertScriptKey(
1284+
ctxb, key, true, asset.ScriptKeyScriptPathChannel,
1285+
)
12841286
if err != nil {
12851287
return fmt.Errorf("unable to insert script "+
12861288
"key: %w", err)
@@ -1312,7 +1314,9 @@ func (a *AuxSweeper) importOutputScriptKeys(desc tapscriptSweepDescs) error {
13121314
log.Debugf("Importing script_keys=%v",
13131315
limitSpewer.Sdump(scriptKey))
13141316

1315-
return a.cfg.AddrBook.InsertScriptKey(ctxb, scriptKey, true)
1317+
return a.cfg.AddrBook.InsertScriptKey(
1318+
ctxb, scriptKey, true, asset.ScriptKeyScriptPathChannel,
1319+
)
13161320
}
13171321

13181322
if err := importScriptKey(desc.firstLevel); err != nil {
@@ -1505,7 +1509,9 @@ func (a *AuxSweeper) importCommitTx(req lnwallet.ResolutionReq,
15051509
// the asset will be materialized in the asset table and show up in the
15061510
// balance correctly.
15071511
ctxb := context.Background()
1508-
err := a.cfg.AddrBook.InsertScriptKey(ctxb, fundingScriptKey, true)
1512+
err := a.cfg.AddrBook.InsertScriptKey(
1513+
ctxb, fundingScriptKey, true, asset.ScriptKeyScriptPathChannel,
1514+
)
15091515
if err != nil {
15101516
return fmt.Errorf("unable to insert script key: %w", err)
15111517
}

tapdb/addrs.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,9 @@ func (t *TapAddressBook) InsertAddrs(ctx context.Context,
268268
InternalKeyID: rawScriptKeyID,
269269
TweakedScriptKey: addr.ScriptKey.SerializeCompressed(),
270270
Tweak: addr.ScriptKeyTweak.Tweak,
271+
KeyType: sqlInt16(
272+
addr.ScriptKeyTweak.Type,
273+
),
271274
})
272275
if err != nil {
273276
return fmt.Errorf("unable to insert script "+
@@ -649,7 +652,8 @@ func (t *TapAddressBook) InsertInternalKey(ctx context.Context,
649652
// it can be recognized as belonging to the wallet when a transfer comes in
650653
// later on.
651654
func (t *TapAddressBook) InsertScriptKey(ctx context.Context,
652-
scriptKey asset.ScriptKey, declaredKnown bool) error {
655+
scriptKey asset.ScriptKey, declaredKnown bool,
656+
keyType asset.ScriptKeyType) error {
653657

654658
var writeTxOpts AddrBookTxOptions
655659
return t.db.ExecTx(ctx, &writeTxOpts, func(q AddrBook) error {
@@ -666,6 +670,7 @@ func (t *TapAddressBook) InsertScriptKey(ctx context.Context,
666670
TweakedScriptKey: scriptKey.PubKey.SerializeCompressed(),
667671
Tweak: scriptKey.Tweak,
668672
DeclaredKnown: sqlBool(declaredKnown),
673+
KeyType: sqlInt16(keyType),
669674
})
670675
return err
671676
})

0 commit comments

Comments
 (0)