Skip to content

Commit cda1bb3

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 0d33b28 commit cda1bb3

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.
@@ -1001,6 +1045,9 @@ type TweakedScriptKey struct {
10011045
// flag has is that assets with a declared key are shown in the asset
10021046
// list/balance.
10031047
DeclaredKnown bool
1048+
1049+
// Type is the type of script key that is being used.
1050+
Type ScriptKeyType
10041051
}
10051052

10061053
// IsEqual returns true is this tweaked script key is exactly equivalent to the
@@ -1086,15 +1133,49 @@ func (s *ScriptKey) HasScriptPath() bool {
10861133
return s.TweakedScriptKey != nil && len(s.TweakedScriptKey.Tweak) > 0
10871134
}
10881135

1136+
// GuessType tries to guess the type of the script key based on the information
1137+
// available.
1138+
func (s *ScriptKey) GuessType() ScriptKeyType {
1139+
// If we have an explicit script key type set, we can return that.
1140+
if s.TweakedScriptKey != nil &&
1141+
s.TweakedScriptKey.Type != ScriptKeyUnknown {
1142+
1143+
return s.TweakedScriptKey.Type
1144+
}
1145+
1146+
// If there is a known tweak, then we know that this is a script path
1147+
// key. We never return the channel type, since those keys should always
1148+
// be declared properly, and we never should need to guess their type.
1149+
if s.HasScriptPath() {
1150+
return ScriptKeyScriptPathExternal
1151+
}
1152+
1153+
// Is it the known NUMS key? Then this is a tombstone output.
1154+
if s.PubKey != nil && s.PubKey.IsEqual(NUMSPubKey) {
1155+
return ScriptKeyTombstone
1156+
}
1157+
1158+
// Do we know the internal key? Then we can check whether it is a
1159+
// BIP-0086 key.
1160+
if s.PubKey != nil && s.TweakedScriptKey != nil &&
1161+
s.TweakedScriptKey.RawKey.PubKey != nil {
1162+
1163+
bip86 := NewScriptKeyBip86(s.TweakedScriptKey.RawKey)
1164+
if bip86.PubKey.IsEqual(s.PubKey) {
1165+
return ScriptKeyBip86
1166+
}
1167+
}
1168+
1169+
return ScriptKeyUnknown
1170+
}
1171+
10891172
// NewScriptKey constructs a ScriptKey with only the publicly available
10901173
// information. This resulting key may or may not have a tweak applied to it.
10911174
func NewScriptKey(key *btcec.PublicKey) ScriptKey {
10921175
// Since we'll never query lnd for a tweaked key, it doesn't matter if
10931176
// we lose the parity information here. And this will only ever be
10941177
// serialized on chain in a 32-bit representation as well.
1095-
key, _ = schnorr.ParsePubKey(
1096-
schnorr.SerializePubKey(key),
1097-
)
1178+
key, _ = schnorr.ParsePubKey(schnorr.SerializePubKey(key))
10981179
return ScriptKey{
10991180
PubKey: key,
11001181
}
@@ -1106,9 +1187,7 @@ func NewScriptKey(key *btcec.PublicKey) ScriptKey {
11061187
func NewScriptKeyBip86(rawKey keychain.KeyDescriptor) ScriptKey {
11071188
// Tweak the script key BIP-0086 style (such that we only commit to the
11081189
// internal key when signing).
1109-
tweakedPubKey := txscript.ComputeTaprootKeyNoScript(
1110-
rawKey.PubKey,
1111-
)
1190+
tweakedPubKey := txscript.ComputeTaprootKeyNoScript(rawKey.PubKey)
11121191

11131192
// Since we'll never query lnd for a tweaked key, it doesn't matter if
11141193
// we lose the parity information here. And this will only ever be
@@ -1121,6 +1200,7 @@ func NewScriptKeyBip86(rawKey keychain.KeyDescriptor) ScriptKey {
11211200
PubKey: tweakedPubKey,
11221201
TweakedScriptKey: &TweakedScriptKey{
11231202
RawKey: rawKey,
1203+
Type: ScriptKeyBip86,
11241204
},
11251205
}
11261206
}
@@ -1508,6 +1588,7 @@ func (a *Asset) Copy() *Asset {
15081588
if a.ScriptKey.TweakedScriptKey != nil {
15091589
assetCopy.ScriptKey.TweakedScriptKey = &TweakedScriptKey{
15101590
DeclaredKnown: a.ScriptKey.DeclaredKnown,
1591+
Type: a.ScriptKey.Type,
15111592
}
15121593
assetCopy.ScriptKey.RawKey = a.ScriptKey.RawKey
15131594

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
@@ -7913,7 +7913,12 @@ func (r *rpcServer) DeclareScriptKey(ctx context.Context,
79137913
err)
79147914
}
79157915

7916-
err = r.cfg.TapAddrBook.InsertScriptKey(ctx, *scriptKey, true)
7916+
// Because we've been given the key over the RPC interface, we can't be
7917+
// 100% sure of the type. But we can make a best effort guess based on
7918+
// the fields the user has set.
7919+
keyType := scriptKey.GuessType()
7920+
7921+
err = r.cfg.TapAddrBook.InsertScriptKey(ctx, *scriptKey, true, keyType)
79177922
if err != nil {
79187923
return nil, fmt.Errorf("error inserting script key: %w", err)
79197924
}

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)