Skip to content

[feature]: resolve inadvertent 1st party universe tree collision mapping #1455

@Roasbeef

Description

@Roasbeef

Is your feature request related to a problem? Please describe.

Today we have an issue where an issuance or transfer event can result in a non-unique Universe key, which can result in some data being lost/clobbered. It's important to note that due to the key structure here, only the sender or transaction creator can trigger this behavior.

To understand the issue, we need to look at the Universe tree structure. For simplicltiy, we'll focus on issuance events here.

For assets that have a group key, the top level key is a special universe ID that's just the group key:

// Identifier is the identifier for a universe.
type Identifier struct {
// AssetID is the asset ID for the universe.
//
// TODO(roasbeef): make both pointers?
AssetID asset.ID
// GroupKey is the group key for the universe.
GroupKey *btcec.PublicKey
// ProofType is the type of proof that should be stored in the universe.
ProofType ProofType
}
// Bytes returns a bytes representation of the ID.
func (i *Identifier) Bytes() [32]byte {
if i.GroupKey != nil {
return sha256.Sum256(schnorr.SerializePubKey(i.GroupKey))
}
return i.AssetID
}

This is used to derive the unique Universe namespace where we store the information on disk.

The leaf key then has the following structure:

// LeafKey is the top level leaf key for a universe. This will be used to key
// into a universe's MS-SMT data structure. The final serialized key is:
// sha256(mintingOutpoint || scriptKey). This ensures that all
// leaves for a given asset will be uniquely keyed in the universe tree.
type LeafKey struct {
// OutPoint is the outpoint at which the asset referenced by this key
// resides.
OutPoint wire.OutPoint
// ScriptKey is the script key of the base asset. If this isn't
// specified, then the caller is attempting to query for all the script
// keys at that minting outpoint.
ScriptKey *asset.ScriptKey
// TODO(roasbeef): add asset type too?
}
// UniverseKey is the key for a universe.
func (b LeafKey) UniverseKey() [32]byte {
// key = sha256(mintingOutpoint || scriptKey)
h := sha256.New()
_ = wire.WriteOutPoint(h, 0, 0, &b.OutPoint)
h.Write(schnorr.SerializePubKey(b.ScriptKey.PubKey))
var k [32]byte
copy(k[:], h.Sum(nil))
return k
}

The issue here pops up when a user issues a new grouped asset, and in the same transaction issues multiple new asset IDs with the same script key. The leaf tuple right now is (outpoint, scriptKey), so only one of those new asset UTXOs will be stored. Instead, we want a structure of (outpoint, assetID, scriptKey). This resembles the structure in the normal asset commitment, wherein the final level still commits to the asset ID for uniqueness purposes.

Describe the solution you'd like

We should fix this at two levels: the RPC service, and the DB schema itself.

First for the RPC service, we'll add a new version to the relevant set of RPC calls. As we'll be changing the leaf key here, we'll end up with a distinct MS-SMT root for the same set of data. The old version will query the old structure on disk, and the new version will use the updated key structure.

As for the DB, we can keep the exact same schema, but we'll start to mix a version into the universe.Identifier struct mentioned above. This will also be carried into the universe.LeafKey structure, where the version will also affect the final key created. V0 is today, v1 adds the asset ID into the leaf specifier.

The final step will be a migration. This migration will operate on the Go level, as we'll just have the code load the instance of the old tree(s), then insert all the leaf items into the new tree.

For the migration, we may want to take a look at this PR for golang-migrate which will enable us to run go code along side the migrations. We'll need to this this effectively to ensure we have the necessary amount of fault tolerance here. Either way, the migration code can be defensive (diff to minimize work needed), or just ignore any upsert errors, etc.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions