Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proof: add new universe commitment TLV fields to asset meta reveal #1335

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

guggero
Copy link
Member

@guggero guggero commented Jan 28, 2025

Depends on #1337.

Fixes #956.

Adds two new fields to the TLV record of the asset's meta reveal:

  • Decimal display: Allows minting of assets with non-JSON meta data as we don't need to rely on writing the field there)
  • Canonical universe URL: Define the "home" universe for an asset that universe commitments will be synced to

Important for reviewers:

  • This PR does not add the RPC or CLI level flags for the two new entries. Those only really make sense with Lock Minting Anchor TX Change Output for Future Universe Commitment Support #1325 merged as well, so we'll add those later.
  • While writing this PR a bug was found in the backward compatible way we use odd TLV records. See e9286f0. Unfortunately that means these new fields will only be backward compatible to v0.5.1 (this PR) and not all the way back to v0.5.0.
  • We prepare for future backward compatibility tests with 293c60f. The idea is to be able to have a CI step that check out an older version (e.g. v0.5.1) in the future and run that test with updated test vectors of the future version, to make sure they can still be parsed and validated by that older version.

@guggero guggero requested a review from ffranr January 28, 2025 14:32
@guggero guggero force-pushed the tlv-decimal-display branch from 9180e85 to e037590 Compare January 28, 2025 14:43
@coveralls
Copy link

coveralls commented Jan 28, 2025

Pull Request Test Coverage Report for Build 13071869152

Details

  • 179 of 292 (61.3%) changed or added relevant lines in 15 files are covered.
  • 16 unchanged lines in 7 files lost coverage.
  • Overall coverage increased (+0.1%) to 40.881%

Changes Missing Coverage Covered Lines Changed/Added Lines %
tapdb/assets_common.go 13 15 86.67%
tapdb/sqlc/assets.sql.go 15 17 88.24%
tapdb/asset_minting.go 8 13 61.54%
itest/utils.go 0 7 0.0%
tapdb/sqlutils.go 6 13 46.15%
tapdb/asset_meta.go 9 18 50.0%
tapgarden/seedling.go 13 22 59.09%
proof/meta.go 25 35 71.43%
itest/assertions.go 0 14 0.0%
rpcserver.go 0 14 0.0%
Files with Coverage Reduction New Missed Lines %
itest/assertions.go 1 0.0%
itest/utils.go 1 0.0%
rpcserver.go 1 0.0%
tappsbt/create.go 2 53.22%
asset/mock.go 3 92.42%
asset/asset.go 3 77.0%
commitment/tap.go 5 83.64%
Totals Coverage Status
Change from base Build 13048553200: 0.1%
Covered Lines: 26951
Relevant Lines: 65925

💛 - Coveralls

Copy link
Contributor

@ffranr ffranr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't reviewed these changes but:

I've been thinking more about universe commitments. I think we should get further along in the design of that feature before deciding on a universe commitment asset metadata TLV field. It might be wise to split out the universe commitment field from this PR. We can catchup on this.

@guggero guggero force-pushed the tlv-decimal-display branch from e037590 to f81318f Compare January 28, 2025 21:11
@guggero guggero changed the base branch from main to tlv-refactor-fixes January 28, 2025 21:11
@guggero guggero deleted the branch lightninglabs:main January 29, 2025 08:40
@guggero guggero closed this Jan 29, 2025
@guggero guggero reopened this Jan 29, 2025
@guggero guggero changed the base branch from tlv-refactor-fixes to main January 29, 2025 08:41
@guggero guggero force-pushed the tlv-decimal-display branch from f81318f to 56e113e Compare January 29, 2025 12:44
@guggero
Copy link
Member Author

guggero commented Jan 29, 2025

@ffranr I've removed the UniverseCommitments boolean field as you suggested.

@guggero guggero requested a review from ffranr January 29, 2025 12:44
Copy link
Contributor

@ffranr ffranr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The canonical universe check might hinder us unnecessarily.

Great to see unit tests covering the previous metadata formulation 👍

proof/meta.go Outdated Show resolved Hide resolved
proof/meta.go Outdated Show resolved Hide resolved
proof/meta_test.go Show resolved Hide resolved
tapgarden/batch.go Show resolved Hide resolved
Comment on lines 213 to 236
// We also require the canonical universe URL and universe commitment
// flag to match.
var (
seedlingCanonicalURL string
anchorCanonicalURL string
)
if seedlingMeta != nil && seedlingMeta.CanonicalUniverse != nil {
seedlingCanonicalURL = seedlingMeta.CanonicalUniverse.String()
}
if anchorMeta != nil && anchorMeta.CanonicalUniverse != nil {
anchorCanonicalURL = anchorMeta.CanonicalUniverse.String()
}

if seedlingCanonicalURL != anchorCanonicalURL {
return fmt.Errorf("seedling canonical universe URL does not "+
"match group anchor: %v, %v", seedlingCanonicalURL,
anchorCanonicalURL)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point, I think, that the anchor meta will be the genesis asset meta. (I check validateGroupKey call site).

This check in the PR enforces that the canonical universe URL for the new seedling matches the one specified in the genesis asset. I don’t think we should impose this restriction—a new minting tranche should be allowed to define its own canonical universe URL.

With that allowance, the minter can update the canonical universe URL.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't necessarily disagree with potentially having the option to update the URL at a later point.
But since we don't know yet how that would affect client code (e.g. when syncing a grouped asset's issuance history, would we need to follow the whole chain to the very last mint to find out what URL to use?), I think we should keep this restriction for now.
This will also help make sure the user doesn't forget to set the URL by accident. Or has a typo on a subsequent mint.

Copy link
Contributor

@ffranr ffranr Feb 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will also help make sure the user doesn't forget to set the URL by accident. Or has a typo on a subsequent mint.

Isn't that precisely why we should allow updating the URL in a subsequent tranche? What’s our current plan if a user mints an asset on-chain and later discovers the URL is incorrect? (We've encountered a similar issue with the proof courier URL in tap addresses.)

Maybe we should consider a universe server (if that's the URL scheme) connection check as part of minting.

From my perspective, when a client attempts to evaluate a mint and has access to a single asset ID within a given group, they also have three potential canonical universe URLs:

  1. The canonical URL of the tranche they are evaluating.
  2. The genesis canonical URL.
  3. The default URL hardcoded into tapd.

In my opinion, tapd should sync with these URLs in the specified order. Having access to multiple URLs, rather than relying solely on the genesis URL, doesn’t seem problematic. From my perspective, the purpose of the canonical URL field is to enhance the likelihood of a successful sync.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point. Not sure about the connection check, could be a bit of a hurdle (perhaps you first want to make sure the mint succeeds before setting up the universe server?), I'd suggest adding that to the PR that adds the CLI/RPC flags for the field in the first place.

Good idea about syncing in order and with decreasing priority if there are multiple sources for a potential canonical universe URL. Going to remove the check for now then.

With this commit we add the decimal display as a new TLV field on the
meta reveal. We'll still encode it within the JSON meta data if the meta
data is of type JSON. For all other types we can now also carry the
value, to make sure we don't put any type constraints on future asset
mints (in case issuers want to use a different format than JSON).

Since we're using TLV, old meta reveals should still be read correctly
and the default value of 0 is assumed for any meta reveal that doesn't
have the explicit record set.
We'll add a unit test for that in the next commit.
With this commit we make sure that a new seedling that attempts to mint
into the same group as a previous mint has the same meta fields.
@guggero guggero force-pushed the tlv-decimal-display branch from 56e113e to 9a2d3c6 Compare January 31, 2025 11:30
@guggero guggero requested a review from ffranr January 31, 2025 11:30
fn/option.go Show resolved Hide resolved

require.Len(t.t, thirdAssets, 1)
require.NotNil(t.t, thirdAssets[0].DecimalDisplay)
require.EqualValues(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also assert that the dec display is still encoded in the asset meta JSON here (still getting through the PR).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also what should be the behavior if the user sets the field manually in the JSON meta? What about if that value differs from the one here in the proto.

// If the decimal display is set as the new TLV value, we can use that
// directly.
if decimalDisplay, isSet := m.DecimalDisplay.Unwrap(); isSet {
return nil, decimalDisplay, nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before returning, should we do a sanity check that:

  • The value isn't already encoded in the asset meta JSON
  • That if it is then it matches this value

?

proof/records.go Outdated Show resolved Hide resolved
proof/records.go Show resolved Hide resolved
// If a custom decimal display is set, the meta type must also be set to
// JSON.
// If a custom decimal display is set, we require the AssetMeta to be
// set. That means the user has to at least specify the meta type.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about assuming it's just the opaque type if the dec display is set? Given that by default (opaque asset type, not JSON), we won't encode this in the asset meta.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, yeah, I guess we could do that now. But that would mean that both the meta data blob and type would be empty/unset and the meta hash would just be TLV record of the decimal display?

rpcserver.go Show resolved Hide resolved
// CanonicalUniverse is the URL of the canonical (approved,
// authoritative) universe where the asset minting and universe
// commitment proofs will be pushed to.
CanonicalUniverse fn.Option[url.URL]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about instead lifting this to the issuance proof itself? We can require that this be signed by the group key so it can't just be swapped out by a 3rd party.

This way, one can update this filed with a new issuance event (or some sort of off-chain proof), and not be locked in from the very start (DNS addr changes, etc).

Thinking about it further, we can even tie this into the unified universe commitment structure. We can add a 4th commitment/field, that can be updated via a spend on chain to distribute new meta/param updates to all the verifiers.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another idea is we just put an authentication/delegation key here. This key can be used in the pre-commitment output, and then can also be used to delegate authentication capabilities to another key. Such abilities can include updating the canonical universe value.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also see discussion here: #1335 (comment)

I guess at this point the field in the meta reveal would be more of a "universe hint" than the "authoritative" universe. The use for it would mostly be bootstrapping:

  1. New tapd comes online, only syncs to the default universe (e.g. the hard coded one)
  2. User wants to learn about grouped asset X, turns on issuance proof sync for just asset X
  3. tapd syncs the issuance proofs for asset X, sees universe URL in meta reveal of those syncs
  4. tapd turns on universe commitment sync for asset X using the universe URL(s) found in the meta reveals

But maybe you have a different flow in mind... Should we just remove the field then if we're not really sure yet how we're going to use it?

Another idea is we just put an authentication/delegation key here.

We are going to use an authenticated key for the pre-commitment key. So perhaps we could also include the universe URL in the actual universe commitment proof, where it would be more "authoritative" and could be updated. But IMO that URL can't only be in the universe commitment proof itself, otherwise you we have a bootstrap issue (you can only get the universe commitment proofs from the universe that then tells you what URL to use to get universe commitment proofs...)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can require that this be signed by the group key so it can't just be swapped out by a 3rd party.

Not sure if that's a good idea UX wise. In case of a cold wallet mint, you'd already need to sign at least 2 PSBTs per mint event (one for the group key VM TX, one spending the previous universe commitment output). So needing to sign yet another transaction just makes things clunky IMO.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess at this point the field in the meta reveal would be more of a "universe hint" than the "authoritative" universe.

The way I've been viewing things is that this is the primary Universe that should be checked on spend to obtain the pre-image of the commitment data, which is this new unified commitment. It should be what people check tomake sure they have all the data, but ofc this data can still be mirrored elsewhere.

tapd syncs the issuance proofs for asset X, sees universe URL in meta reveal of those syncs
tapd turns on universe commitment sync for asset X using the universe URL(s) found in the meta reveals

So with my suggestion, rather than seeing it in the meta, they'd see it in a singed section in the proof metadata. To go a step further, we might also augment the genesis virtual tx to commit to this data.

One thing that we still haven't spec'd out, is the RPC API for fetching this new unified commitment root, the sub-trees and the leaves of those sub-trees. The "normal" Universes, may not offer this specialized API, though to be maximally useful, they should mirror it from the canonical universe.

So perhaps we could also include the universe URL in the actual universe commitment proof, where it would be more "authoritative" and could be updated

This sounds similar to the idea I mentioned earlier about having a param sub-tree/commitment. This is effectively authenticated, as it can only be updated by being able to spend the pre-commitment tree.

However, as verifiers will first sync the normal issuance proofs, we need to provide them with enough information to then be able to locate + authenticate the canonical Universe. The issuance proofs can include a signed payload that contains this bootstrap + discovery information (including what key to use to verify.

But IMO that URL can't only be in the universe commitment proof itself, otherwise you we have a bootstrap issue (you can only get the universe commitment proofs from the universe that then tells you what URL to use to get universe commitment proofs...)

I don't see how a bootstrap issue arises. You use the existing normal Universes to fetch the issuance proofs, etc. But these may not have burn, and the new meta/param tree we've been ideating. To be able to properly verify the commitment spends on chain, you look to this canonical Universe.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if that's a good idea UX wise. In case of a cold wallet mint, you'd already need to sign at least 2 PSBTs per mint event (one for the group key VM TX, one spending the previous universe commitment output). So needing to sign yet another transaction just makes things clunky IMO.

Agreed. So one way we can avoid that clunkiness is allowing a delegation key to be presented in the initial meta. This key is then the one that signs the set of booststrap params, and can even include yet another delegation key in the set of params, etc.

@Roasbeef
Copy link
Member

Roasbeef commented Feb 5, 2025

Bringing this outside the thread so it doesn't get lost.

Here's my idea w.r.t what should go into the meta, how bootstrapping would work, and this new bootstrap params field:

  • What goes inside the meta:

    • A bool that is used to communicate to verifiers that the issuer is opting into the new on-chain commitment rules.
    • A delegation key. This is used to create+verify the initial pre-commitment, and also to sign a new payload that goes into the issuance proof itself.
  • New issuance proof payload:

    • We add a new TLV field to the issuance proof, call this commitment_params for now (random name, open to other suggestions).
    • This can be updated with every new issuance proof.
      • Alternatively, this goes in the unified commitment, either as a new sub-tree, or is the first use of the "reserved" opaque hash I've mentioned before. I think @guggero suggested a form of this above.
    • To start with, it includes the following fields:
      • canonical_universe_url: same thing we've added here in this PR. Can be updated with another issuance, or a param spend of the current on-chain commitment.
        • The param spend is an off-chain event that triggers an on-chain spend, just like updating the ignore list.
      • delegate_key: specifies which key to use for the pre-commitment creation+verification. If blank, use the group key.
        • We can support many of these, or add some other field later re a threshold or w/e. May also be a musig key, etc.
    • If it goes into the issuance proof, then we also include a signature of this payload:
      • If it has a delegation key, you verify with that, otherwise use the group key (allows for the external flow with minimal round trips for issuance).
      • To make sure this value can't be omitted, we either embed a hash of it, or always expect a signed value, even if it's nil.

@guggero
Copy link
Member Author

guggero commented Feb 5, 2025

Trying to summarize this thread and combine it with the internal design doc.
This is the current version @ffranr and I came up with and seem to agree on:

Initial mint

  • Asset metadata
    • UniverseCommitments: bool
    • DelegationKey: PubKey (mandatory if above is true, defines a bare/"internal" key that is authorized to sign)
  • Minting batch
    • Change is locked to DelegationKey + bip86Tweak(DelegationKey)
  • Issuance proof
    • Commitment params (hashed and signed with bare DelegationKey)
      • CanonicalUniverseURL: string
      • PreCommitmentIndex: uint32 --> Output index of the pre-commitment (minting batch change output)
    • CommitmentParamsSig: [64]byte --> Signature with bare DelegationKey for sha256(minting_outpoint || serialized_commitment_payload) where serialized_commitment_payload is the TLV encoded bytes of the above fields, or an empty byte slice if no params are defined.
  • Universe commitment proof
    • Commitment data (burns, ignores and so on, hashed and signed with bare DelegationKey)
  • Universe commitment TX
    • Spends pre-commitment output
    • Output is locked to DelegationKey + tapTweak(tapRoot(commitment leaf))
    • Change output goes back to wallet

Subsequent mint

  • Asset metadata
    • UniverseCommitments: bool (enforced if group anchor has enabled)
    • DelegationKey: PubKey (mandatory if above is true, defines a bare/"internal" key that is authorized to sign)
  • Minting batch
    • Change is locked to DelegationKey + bip86Tweak(DelegationKey)
  • Issuance proof
    • Commitment params (hashed and signed with bare DelegationKey)
      • CanonicalUniverseURL: string --> Could be a new URL
      • PreCommitmentIndex: uint32 --> Output index of the pre-commitment (minting batch change output)
    • CommitmentParamsSig: [64]byte --> Signature with bare DelegationKey for sha256(minting_outpoint || serialized_commitment_payload) where serialized_commitment_payload is the TLV encoded bytes of the above fields, or an empty byte slice if no params are defined.
  • Universe commitment proof
    • Commitment data (burns, ignores and so on, hashed and signed with bare DelegationKey)
  • Universe commitment TX
    • Spends group anchor's universe commitment output (needs to sign with delegation internal key)
    • Output is locked to DelegationKey + tapTweak(tapRoot(commitment leaf))
    • Change output goes back to wallet

Here's my original version I posted with some questions that were then discussed offline.
Main diff do above is:

  • Only one delegation key per issuance
  • Delegation key is a bare/internal key not a Taproot output key
  • Commitment outputs aren't locked to group key but delegation key instead.
  • Added minting_outpoint as a nonce to the hash that's signed in the issuance proof.
Old version

Initial mint

  • Asset metadata
  • Minting batch
    • Change is locked to IssuanceDelegationKey (UniCommitTRKey) or group key if not present
  • Issuance proof
    • Commitment params (hashed and signed with IssuanceDelegationKey or group key if not present)
      • CanonicalUniverseURL: string
      • CommitmentDelegationKey: PubKey --> Delegation key for universe commitment proof
      • PreCommitmentIndex: uint32 --> Output index of the pre-commitment (minting batch change output)
    • CommitmentParamsSig: [64]byte
  • Universe commitment proof
    • Commitment data (burns, ignores and so on, hashed and signed with CommitmentDelegationKey or group key if ont present)
  • Universe commitment TX
    • Spends pre-commitment output
    • Output is locked to group key + commitment leaf
    • Change output goes back to wallet

Subsequent mint

  • Asset metadata
  • Minting batch
    • Change is locked to IssuanceDelegationKey (UniCommitTRKey) or group key if not present
  • Issuance proof
    • Commitment params (hashed and signed with IssuanceDelegationKey or group key if not present)
      • CanonicalUniverseURL: string --> Could be a new URL
      • CommitmentDelegationKey: PubKey --> Delegation key for universe commitment proof, could be a new one or re-use the same as in the group anchor
      • PreCommitmentIndex: uint32 --> Output index of the pre-commitment (minting batch change output)
    • CommitmentParamsSig: [64]byte
  • Universe commitment proof
    • Commitment data (burns, ignores and so on, hashed and signed with CommitmentDelegationKey or group key if ont present)
  • Universe commitment TX
    • Spends group anchor's universe commitment output (needs to sign with group internal key)
    • Output is locked to group internal key + commitment leaf
    • Change output goes back to wallet

My main question here is: Do we really need two different delegation keys? One in the asset metadata and then a new one in the issuance proof's commitment params? I only differentiate between those two keys because @Roasbeef mentioned two delegation keys above. But I think this could be the same one? So just one delegation key defined in the asset meta that signs for both the issuance proof payload and the universe commitment payload.
But each minting tranche could have its new delegation key, so it can be changed with every mint, along with the canonical universe URL.

Second question: Do we still want to lock the actual universe commitment transaction output to the group key (plus a leaf that contains the commitment roots)? Or would that also lock to the delegated key?

@ffranr
Copy link
Contributor

ffranr commented Feb 5, 2025

I think I pretty much agree with Oli's summary. Except that I don't think we should ever lock any uni commitment related output with the group key. Because of the external cold key signing requirements.

And I don't see the point in two different delegation keys also.

I think the canonical universe URL should go in the issuance proof (signed payload) and then we should be able to update it in the universe commitment proof. So I don't think the bootstrapping issue is a blocker.

Thinking about it further, we can even tie this into the unified universe commitment structure. We can add a 4th commitment/field, that can be updated via a spend on chain to distribute new meta/param updates to all the verifiers.

I agree with this sort of idea. I think we should generalise the whole universe commitment notion to something that the minter can use to issue general updates (update canonical uni URL, update ignore list, update total issuance, etc.)

@ffranr
Copy link
Contributor

ffranr commented Feb 5, 2025

Just thinking outloud:

I think what we need in the asset meta data is:

  • the delegation key
  • bool flag for: does the issuance proof contain a payload signed with that delegation key. (we need this encase a melicious universe server strips the signed payload out completely. This is a more general UniverseCommitments=true)

And then in the signed payload in the issuance proof we can have:

  • uni pre-commitment output index: fn.Option[uint]
  • (first/latest) canonical uni URL
  • asset ID/anchor first prev out txid to ensure sig is unique?

And then the signature field for the payload.
So it's like a general signed payload with a general delegated key. And all we're doing is linking the delegation key to the group key. And we assure that the payload sig is unique to that issuence
using the asset ID as the nonce

@guggero
Copy link
Member Author

guggero commented Feb 5, 2025

I updated my comment above with what we discussed. Thanks for your inputs!

@ffranr
Copy link
Contributor

ffranr commented Feb 5, 2025

I don't think the CanonicalUniverseURL should be in the issuance proof signed payload. I think it should be in the asset metadata.

To be clear, I'm think of a minter who:

  • want's to specify decimal display
  • want's to specify canonical URL
  • does not enable uni commitments.

If we force the URL to be in the issuance proof signed payload they will have to manage a delegation key (at least for now?), which they don't necessarily need to manage.

I guess it comes down to: is specifying canonical URL and not enabling uni commitments reasonable? And I think the answer is yes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: 👀 In review
Development

Successfully merging this pull request may close these issues.

universe - [feature]: Add new TLV fields for universe commitments to asset metadata
4 participants