Skip to content

Commit 58b35e6

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 3f011d5 commit 58b35e6

File tree

12 files changed

+242
-38
lines changed

12 files changed

+242
-38
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.DetermineType()
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: 93 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.
@@ -1013,6 +1057,9 @@ type TweakedScriptKey struct {
10131057
// flag has is that assets with a declared key are shown in the asset
10141058
// list/balance.
10151059
DeclaredKnown bool
1060+
1061+
// Type is the type of script key that is being used.
1062+
Type ScriptKeyType
10161063
}
10171064

10181065
// IsEqual returns true is this tweaked script key is exactly equivalent to the
@@ -1098,15 +1145,55 @@ func (s *ScriptKey) HasScriptPath() bool {
10981145
return s.TweakedScriptKey != nil && len(s.TweakedScriptKey.Tweak) > 0
10991146
}
11001147

1148+
// DetermineType attempts to determine the type of the script key based on the
1149+
// information available. This method will only return ScriptKeyUnknown if the
1150+
// following condition is met:
1151+
// - The script key doesn't have a script path, but the final Taproot output
1152+
// key doesn't match a BIP-0086 key derived from the internal key. This will
1153+
// be the case for "foreign" script keys we import from proofs, where we set
1154+
// the internal key to the same key as the tweaked script key (because we
1155+
// don't know the internal key, as it's not part of the proof encoding).
1156+
func (s *ScriptKey) DetermineType() ScriptKeyType {
1157+
// If we have an explicit script key type set, we can return that.
1158+
if s.TweakedScriptKey != nil &&
1159+
s.TweakedScriptKey.Type != ScriptKeyUnknown {
1160+
1161+
return s.TweakedScriptKey.Type
1162+
}
1163+
1164+
// If there is a known tweak, then we know that this is a script path
1165+
// key. We never return the channel type, since those keys should always
1166+
// be declared properly, and we never should need to guess their type.
1167+
if s.HasScriptPath() {
1168+
return ScriptKeyScriptPathExternal
1169+
}
1170+
1171+
// Is it the known NUMS key? Then this is a tombstone output.
1172+
if s.PubKey != nil && s.PubKey.IsEqual(NUMSPubKey) {
1173+
return ScriptKeyTombstone
1174+
}
1175+
1176+
// Do we know the internal key? Then we can check whether it is a
1177+
// BIP-0086 key.
1178+
if s.PubKey != nil && s.TweakedScriptKey != nil &&
1179+
s.TweakedScriptKey.RawKey.PubKey != nil {
1180+
1181+
bip86 := NewScriptKeyBip86(s.TweakedScriptKey.RawKey)
1182+
if bip86.PubKey.IsEqual(s.PubKey) {
1183+
return ScriptKeyBip86
1184+
}
1185+
}
1186+
1187+
return ScriptKeyUnknown
1188+
}
1189+
11011190
// NewScriptKey constructs a ScriptKey with only the publicly available
11021191
// information. This resulting key may or may not have a tweak applied to it.
11031192
func NewScriptKey(key *btcec.PublicKey) ScriptKey {
11041193
// Since we'll never query lnd for a tweaked key, it doesn't matter if
11051194
// we lose the parity information here. And this will only ever be
11061195
// serialized on chain in a 32-bit representation as well.
1107-
key, _ = schnorr.ParsePubKey(
1108-
schnorr.SerializePubKey(key),
1109-
)
1196+
key, _ = schnorr.ParsePubKey(schnorr.SerializePubKey(key))
11101197
return ScriptKey{
11111198
PubKey: key,
11121199
}
@@ -1118,9 +1205,7 @@ func NewScriptKey(key *btcec.PublicKey) ScriptKey {
11181205
func NewScriptKeyBip86(rawKey keychain.KeyDescriptor) ScriptKey {
11191206
// Tweak the script key BIP-0086 style (such that we only commit to the
11201207
// internal key when signing).
1121-
tweakedPubKey := txscript.ComputeTaprootKeyNoScript(
1122-
rawKey.PubKey,
1123-
)
1208+
tweakedPubKey := txscript.ComputeTaprootKeyNoScript(rawKey.PubKey)
11241209

11251210
// Since we'll never query lnd for a tweaked key, it doesn't matter if
11261211
// we lose the parity information here. And this will only ever be
@@ -1133,6 +1218,7 @@ func NewScriptKeyBip86(rawKey keychain.KeyDescriptor) ScriptKey {
11331218
PubKey: tweakedPubKey,
11341219
TweakedScriptKey: &TweakedScriptKey{
11351220
RawKey: rawKey,
1221+
Type: ScriptKeyBip86,
11361222
},
11371223
}
11381224
}
@@ -1527,6 +1613,7 @@ func (a *Asset) Copy() *Asset {
15271613
if a.ScriptKey.TweakedScriptKey != nil {
15281614
assetCopy.ScriptKey.TweakedScriptKey = &TweakedScriptKey{
15291615
DeclaredKnown: a.ScriptKey.DeclaredKnown,
1616+
Type: a.ScriptKey.Type,
15301617
}
15311618
assetCopy.ScriptKey.RawKey = a.ScriptKey.RawKey
15321619

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
@@ -8134,7 +8134,12 @@ func (r *rpcServer) DeclareScriptKey(ctx context.Context,
81348134
err)
81358135
}
81368136

8137-
err = r.cfg.TapAddrBook.InsertScriptKey(ctx, *scriptKey, true)
8137+
// Because we've been given the key over the RPC interface, we can't be
8138+
// 100% sure of the type. But we can make a best effort guess based on
8139+
// the fields the user has set.
8140+
keyType := scriptKey.DetermineType()
8141+
8142+
err = r.cfg.TapAddrBook.InsertScriptKey(ctx, *scriptKey, true, keyType)
81388143
if err != nil {
81398144
return nil, fmt.Errorf("error inserting script key: %w", err)
81408145
}

tapchannel/aux_closer.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -560,12 +560,6 @@ func (a *AuxChanCloser) ShutdownBlob(
560560
return none, err
561561
}
562562

563-
err = a.cfg.AddrBook.InsertScriptKey(ctx, newKey, true)
564-
if err != nil {
565-
return none, fmt.Errorf("error declaring script key: "+
566-
"%w", err)
567-
}
568-
569563
scriptKeys[assetID] = *newKey.PubKey
570564
}
571565

tapchannel/aux_sweeper.go

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

12651265
ctxb := context.Background()
12661266
for _, key := range keysToImport {
1267-
err := a.cfg.AddrBook.InsertScriptKey(ctxb, key, true)
1267+
err := a.cfg.AddrBook.InsertScriptKey(
1268+
ctxb, key, true, asset.ScriptKeyScriptPathChannel,
1269+
)
12681270
if err != nil {
12691271
return fmt.Errorf("unable to insert script "+
12701272
"key: %w", err)
@@ -1296,7 +1298,9 @@ func (a *AuxSweeper) importOutputScriptKeys(desc tapscriptSweepDescs) error {
12961298
log.Debugf("Importing script_keys=%v",
12971299
limitSpewer.Sdump(scriptKey))
12981300

1299-
return a.cfg.AddrBook.InsertScriptKey(ctxb, scriptKey, true)
1301+
return a.cfg.AddrBook.InsertScriptKey(
1302+
ctxb, scriptKey, true, asset.ScriptKeyScriptPathChannel,
1303+
)
13001304
}
13011305

13021306
if err := importScriptKey(desc.firstLevel); err != nil {
@@ -1489,7 +1493,9 @@ func (a *AuxSweeper) importCommitTx(req lnwallet.ResolutionReq,
14891493
// the asset will be materialized in the asset table and show up in the
14901494
// balance correctly.
14911495
ctxb := context.Background()
1492-
err := a.cfg.AddrBook.InsertScriptKey(ctxb, fundingScriptKey, true)
1496+
err := a.cfg.AddrBook.InsertScriptKey(
1497+
ctxb, fundingScriptKey, true, asset.ScriptKeyScriptPathChannel,
1498+
)
14931499
if err != nil {
14941500
return fmt.Errorf("unable to insert script key: %w", err)
14951501
}

tapchannel/commitment.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1494,7 +1494,9 @@ func deriveFundingScriptKey(ctx context.Context, addrBook address.Storage,
14941494
// We'll also need to import the funding script key into the wallet so
14951495
// the asset will be materialized in the asset table and show up in the
14961496
// balance correctly.
1497-
err := addrBook.InsertScriptKey(ctx, fundingScriptKey, true)
1497+
err := addrBook.InsertScriptKey(
1498+
ctx, fundingScriptKey, true, asset.ScriptKeyScriptPathChannel,
1499+
)
14981500
if err != nil {
14991501
return asset.ScriptKey{}, fmt.Errorf("unable to insert script "+
15001502
"key: %w", err)

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)