@@ -150,8 +150,85 @@ const (
150150 // Keys related to channels are not shown in asset balances (unless
151151 // specifically requested) and are _never_ used for coin selection.
152152 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+ }
153186)
154187
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+
155232var (
156233 // ZeroPrevID is the blank prev ID used for genesis assets and also
157234 // asset split leaves.
@@ -392,7 +469,7 @@ func NewSpecifierFromGroupKey(groupPubKey btcec.PublicKey) Specifier {
392469 }
393470}
394471
395- // NewExlusiveSpecifier creates a specifier that may only include one of asset
472+ // NewExclusiveSpecifier creates a specifier that may only include one of asset
396473// ID or group key. If both are set then a specifier over the group key is
397474// created.
398475func NewExclusiveSpecifier (id * ID ,
@@ -1043,6 +1120,65 @@ func EqualKeyDescriptors(a, o keychain.KeyDescriptor) bool {
10431120 return a .PubKey .IsEqual (o .PubKey )
10441121}
10451122
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+
10461182// TweakedScriptKey is an embedded struct which is primarily used by wallets to
10471183// be able to keep track of the tweak of a script key alongside the raw key
10481184// derivation information.
@@ -1142,14 +1278,16 @@ func (s *ScriptKey) HasScriptPath() bool {
11421278}
11431279
11441280// 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:
11471283// - The script key doesn't have a script path, but the final Taproot output
11481284// key doesn't match a BIP-0086 key derived from the internal key. This will
11491285// be the case for "foreign" script keys we import from proofs, where we set
11501286// the internal key to the same key as the tweaked script key (because we
11511287// 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 {
11531291 // If we have an explicit script key type set, we can return that.
11541292 if s .TweakedScriptKey != nil &&
11551293 s .TweakedScriptKey .Type != ScriptKeyUnknown {
@@ -1178,6 +1316,24 @@ func (s *ScriptKey) DetermineType() ScriptKeyType {
11781316 if bip86 .PubKey .IsEqual (s .PubKey ) {
11791317 return ScriptKeyBip86
11801318 }
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+ }
11811337 }
11821338
11831339 return ScriptKeyUnknown
0 commit comments