@@ -150,8 +150,85 @@ const (
150
150
// Keys related to channels are not shown in asset balances (unless
151
151
// specifically requested) and are _never_ used for coin selection.
152
152
ScriptKeyScriptPathChannel ScriptKeyType = 5
153
+
154
+ // ScriptKeyUniquePedersen is the script key type used for assets that
155
+ // use a unique script key, tweaked with a Pedersen commitment key in a
156
+ // single Tapscript leaf. This is used to avoid collisions in the
157
+ // universe when there are multiple grouped asset UTXOs within the same
158
+ // on-chain output.
159
+ ScriptKeyUniquePedersen ScriptKeyType = 6
160
+ )
161
+
162
+ var (
163
+ // AllScriptKeyTypes is a slice of all known script key types.
164
+ AllScriptKeyTypes = []ScriptKeyType {
165
+ ScriptKeyUnknown ,
166
+ ScriptKeyBip86 ,
167
+ ScriptKeyScriptPathExternal ,
168
+ ScriptKeyBurn ,
169
+ ScriptKeyTombstone ,
170
+ ScriptKeyScriptPathChannel ,
171
+ ScriptKeyUniquePedersen ,
172
+ }
173
+
174
+ // ScriptKeyTypesNoChannel is a slice of all known script key types
175
+ // that are not related to channels. This is used to filter out channel
176
+ // related script keys when querying for assets that are not related to
177
+ // channels.
178
+ ScriptKeyTypesNoChannel = []ScriptKeyType {
179
+ ScriptKeyUnknown ,
180
+ ScriptKeyBip86 ,
181
+ ScriptKeyScriptPathExternal ,
182
+ ScriptKeyBurn ,
183
+ ScriptKeyTombstone ,
184
+ ScriptKeyUniquePedersen ,
185
+ }
153
186
)
154
187
188
+ // ScriptKeyTypeForDatabaseQuery returns a slice of script key types that should
189
+ // be used when querying the database for assets. The returned slice will either
190
+ // contain all script key types or only those that are not related to channels,
191
+ // depending on the `excludeChannelRelated` parameter. Unless the user specifies
192
+ // a specific script key type, in which case the returned slice will only
193
+ // contain that specific script key type.
194
+ func ScriptKeyTypeForDatabaseQuery (excludeChannelRelated bool ,
195
+ userSpecified fn.Option [ScriptKeyType ]) []ScriptKeyType {
196
+
197
+ // If the user specified a script key type, we use that directly to
198
+ // filter the results.
199
+ if userSpecified .IsSome () {
200
+ specifiedType := userSpecified .UnwrapOr (ScriptKeyUnknown )
201
+ dbTypes := []ScriptKeyType {
202
+ specifiedType ,
203
+ }
204
+
205
+ // If the user specifically requested BIP-86 script keys, we
206
+ // also include the Pedersen unique script key type, because
207
+ // those can be spent the same way as BIP-86 script keys, and
208
+ // they should be treated the same way as BIP-86 script keys.
209
+ if specifiedType == ScriptKeyBip86 {
210
+ dbTypes = append (dbTypes , ScriptKeyUniquePedersen )
211
+ }
212
+
213
+ return dbTypes
214
+ }
215
+
216
+ // For some queries, we want to get all the assets with all possible
217
+ // script key types. For those, we use the full set of script key types.
218
+ dbTypes := fn .CopySlice (AllScriptKeyTypes )
219
+
220
+ // For some RPCs (mostly balance related), we exclude the assets that
221
+ // are specifically used for funding custom channels by default. The
222
+ // balance of those assets is reported through lnd channel balance.
223
+ // Those assets are identified by the specific script key type for
224
+ // channel keys. We exclude them unless explicitly queried for.
225
+ if excludeChannelRelated {
226
+ dbTypes = fn .CopySlice (ScriptKeyTypesNoChannel )
227
+ }
228
+
229
+ return dbTypes
230
+ }
231
+
155
232
var (
156
233
// ZeroPrevID is the blank prev ID used for genesis assets and also
157
234
// asset split leaves.
@@ -392,7 +469,7 @@ func NewSpecifierFromGroupKey(groupPubKey btcec.PublicKey) Specifier {
392
469
}
393
470
}
394
471
395
- // NewExlusiveSpecifier creates a specifier that may only include one of asset
472
+ // NewExclusiveSpecifier creates a specifier that may only include one of asset
396
473
// ID or group key. If both are set then a specifier over the group key is
397
474
// created.
398
475
func NewExclusiveSpecifier (id * ID ,
@@ -1043,6 +1120,65 @@ func EqualKeyDescriptors(a, o keychain.KeyDescriptor) bool {
1043
1120
return a .PubKey .IsEqual (o .PubKey )
1044
1121
}
1045
1122
1123
+ // ScriptKeyDerivationMethod is the method used to derive the script key of an
1124
+ // asset send output from the recipient's internal key and the asset ID of
1125
+ // the output. This is used to ensure that the script keys are unique for each
1126
+ // asset ID, so that proofs can be fetched from the universe without collisions.
1127
+ type ScriptKeyDerivationMethod uint8
1128
+
1129
+ const (
1130
+ // ScriptKeyDerivationUniquePedersen means the script key is derived
1131
+ // using the address's recipient ID key and a single leaf that contains
1132
+ // an un-spendable Pedersen commitment key
1133
+ // (OP_CHECKSIG <NUMS_key + asset_id * G>). This can be used to
1134
+ // create unique script keys for each virtual packet in the fragment,
1135
+ // to avoid proof collisions in the universe, where the script keys
1136
+ // should be spendable by a hardware wallet that only supports
1137
+ // miniscript policies for signing P2TR outputs.
1138
+ ScriptKeyDerivationUniquePedersen ScriptKeyDerivationMethod = 0
1139
+ )
1140
+
1141
+ // DeriveUniqueScriptKey derives a unique script key for the given asset ID
1142
+ // using the recipient's internal key and the specified derivation method.
1143
+ func DeriveUniqueScriptKey (internalKey btcec.PublicKey , assetID ID ,
1144
+ method ScriptKeyDerivationMethod ) (ScriptKey , error ) {
1145
+
1146
+ switch method {
1147
+ // For the unique Pedersen method, we derive the script key using the
1148
+ // internal key and the asset ID using a Pedersen commitment key in a
1149
+ // single OP_CHECKSIG leaf.
1150
+ case ScriptKeyDerivationUniquePedersen :
1151
+ leaf , err := NewNonSpendableScriptLeaf (
1152
+ PedersenVersion , assetID [:],
1153
+ )
1154
+ if err != nil {
1155
+ return ScriptKey {}, fmt .Errorf ("unable to create " +
1156
+ "non-spendable leaf: %w" , err )
1157
+ }
1158
+
1159
+ rootHash := leaf .TapHash ()
1160
+ scriptPubKey , _ := schnorr .ParsePubKey (schnorr .SerializePubKey (
1161
+ txscript .ComputeTaprootOutputKey (
1162
+ & internalKey , rootHash [:],
1163
+ ),
1164
+ ))
1165
+ return ScriptKey {
1166
+ PubKey : scriptPubKey ,
1167
+ TweakedScriptKey : & TweakedScriptKey {
1168
+ RawKey : keychain.KeyDescriptor {
1169
+ PubKey : & internalKey ,
1170
+ },
1171
+ Tweak : rootHash [:],
1172
+ Type : ScriptKeyUniquePedersen ,
1173
+ },
1174
+ }, nil
1175
+
1176
+ default :
1177
+ return ScriptKey {}, fmt .Errorf ("unknown script key derivation " +
1178
+ "method: %d" , method )
1179
+ }
1180
+ }
1181
+
1046
1182
// TweakedScriptKey is an embedded struct which is primarily used by wallets to
1047
1183
// be able to keep track of the tweak of a script key alongside the raw key
1048
1184
// derivation information.
@@ -1142,14 +1278,16 @@ func (s *ScriptKey) HasScriptPath() bool {
1142
1278
}
1143
1279
1144
1280
// DetermineType attempts to determine the type of the script key based on the
1145
- // information available. This method will only return ScriptKeyUnknown if the
1146
- // following condition is met:
1281
+ // information available. This method will only return ScriptKeyUnknown if one
1282
+ // of the following conditions is met:
1147
1283
// - The script key doesn't have a script path, but the final Taproot output
1148
1284
// key doesn't match a BIP-0086 key derived from the internal key. This will
1149
1285
// be the case for "foreign" script keys we import from proofs, where we set
1150
1286
// the internal key to the same key as the tweaked script key (because we
1151
1287
// don't know the internal key, as it's not part of the proof encoding).
1152
- func (s * ScriptKey ) DetermineType () ScriptKeyType {
1288
+ // - No asset ID was provided (because it is unavailable in the given
1289
+ // context), and the script key is a unique Pedersen-based key.
1290
+ func (s * ScriptKey ) DetermineType (id * ID ) ScriptKeyType {
1153
1291
// If we have an explicit script key type set, we can return that.
1154
1292
if s .TweakedScriptKey != nil &&
1155
1293
s .TweakedScriptKey .Type != ScriptKeyUnknown {
@@ -1178,6 +1316,24 @@ func (s *ScriptKey) DetermineType() ScriptKeyType {
1178
1316
if bip86 .PubKey .IsEqual (s .PubKey ) {
1179
1317
return ScriptKeyBip86
1180
1318
}
1319
+
1320
+ // If we have the asset's ID, we can check whether this is a
1321
+ // Pedersen-based key. If we don't have the ID, then we can't
1322
+ // determine the type, so we'll end up in the default return
1323
+ // below.
1324
+ if id != nil {
1325
+ scriptKey , err := DeriveUniqueScriptKey (
1326
+ * s .TweakedScriptKey .RawKey .PubKey , * id ,
1327
+ ScriptKeyDerivationUniquePedersen ,
1328
+ )
1329
+ if err != nil {
1330
+ return ScriptKeyUnknown
1331
+ }
1332
+
1333
+ if scriptKey .PubKey .IsEqual (s .PubKey ) {
1334
+ return ScriptKeyUniquePedersen
1335
+ }
1336
+ }
1181
1337
}
1182
1338
1183
1339
return ScriptKeyUnknown
0 commit comments