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

commitment: Add support for minting new Taro commitments #23

Merged
merged 9 commits into from
May 12, 2022

Conversation

wpaulino
Copy link
Contributor

AssetCommitment represents the inner MS-SMT within the Taro protocol committing to a set of assets under the same ID/family. Assets within this tree, which are leaves represented as the serialized asset TLV payload, are keyed by their asset_script_key.

TaroCommitment represents the outer MS-SMT within the Taro protocol committing to a set of asset commitments. Asset commitments, which are leaves represented as asset_version || asset_tree_root || asset_sum, are keyed by their asset_family_key or asset_id otherwise.

@wpaulino
Copy link
Contributor Author

Looking for feedback on whether these are good abstractions to move forward with. I haven't written any tests for this yet since we don't have any test vectors and the tests would just end up reusing the same internal routines as the code itself, which doesn't achieve much. Ideally the test vectors expose the actual byte streams so that we can verify correctness that way.

@wpaulino wpaulino requested a review from Roasbeef April 19, 2022 21:56
@wpaulino wpaulino force-pushed the commitment-mint branch 2 times, most recently from a7efc73 to 19b1621 Compare April 21, 2022 02:06
@wpaulino
Copy link
Contributor Author

Pushed a new update using the initial abstractions added to implement verification, I think they fit quite nicely. With what's present in the current diff, we should have enough to start writing some tests demonstrating inclusion and exclusion asset proofs within Taro commitments. I'll likely get to this after a first review pass.

@Roasbeef
Copy link
Member

With what's present in the current diff, we should have enough to start writing some tests demonstrating inclusion and exclusion asset proofs within Taro commitments.

Dope! Looks like the creation of the 1-of-1 holographic beefzard card isn't too far off...

Thinking more about collectibles, it may make sense to eventually define structured proofs either as part of the asset meta or the body. As an example, lets say I want to do something similar to the keybase sig chain, and link the creation of said collectible to my other verified internets identities (github, twitter, keybase, etc).

asset/asset.go Show resolved Hide resolved
commitment/asset.go Outdated Show resolved Hide resolved
commitment/asset.go Outdated Show resolved Hide resolved
var rootSum [8]byte
binary.BigEndian.PutUint64(rootSum[:], c.TreeRoot.NodeSum())
leafParts := [][]byte{
{byte(c.Version)}, TaroMarker[:], rootHash[:], rootSum[:],
Copy link
Member

Choose a reason for hiding this comment

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

I've been thinking of even using an invalid leaf version here, which would make the verification of commitment uniqueness as bit easier...

Copy link
Member

Choose a reason for hiding this comment

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

Though the market byte also already enables this type of verification.

return txscript.AssembleTaprootScriptTree(commitmentLeaf).
RootNode.TapHash()
}
// TODO: Expose an easy way to construct merkle proofs for this
Copy link
Member

Choose a reason for hiding this comment

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

👍

After needing to work around the tapscript structure to make uniqueness of the commitment possible, I think it might've been a mistake to enforce the ordering property here during hashing. You only need 1 bit of information to determine the order or the leaves, which is pretty trivial in terms of the amount of bytes added to the control block proof (a bit string of left vs right for each hashed element).

c'est la vie I guess

commitment/mint.go Show resolved Hide resolved
commitment/proof.go Outdated Show resolved Hide resolved
commitment/proof.go Outdated Show resolved Hide resolved
commitment/proof.go Outdated Show resolved Hide resolved
@wpaulino wpaulino force-pushed the commitment-mint branch 2 times, most recently from 7a21d88 to 7847330 Compare April 26, 2022 00:57
@wpaulino
Copy link
Contributor Author

@Roasbeef most of the verification part of this diff has been moved to a new branch as I work through the rest of the Taro proof file format. I'll be opening another PR incorporating those changes later on.

@lightninglabs-deploy
Copy link

@wpaulino, remember to re-request review from reviewers when ready

@Roasbeef
Copy link
Member

Roasbeef commented May 4, 2022

Has some linter failures w/ CI.

@wpaulino
Copy link
Contributor Author

wpaulino commented May 5, 2022

@Roasbeef

commitment/mint.go Outdated Show resolved Hide resolved
@wpaulino
Copy link
Contributor Author

This now depends on #29.

@wpaulino wpaulino requested a review from Roasbeef May 11, 2022 00:19
Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

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

Been using the contents of this PR in a local branch and haven't run into any fundamental issues w/ the API.

The one thing I realized along the way is that we shouldn't be using the raw schnorr keys as keys into any level of SMT, as the x-coordinate isn't uniformly random (you'd need to use things like elligator to achieve that). I've noted this at the relevant areas.

My only other main comment is surrounding what appears to be implicit non-inclusion proof behavior when asking for a proof of an asset that doesn't exist.

asset/asset.go Show resolved Hide resolved
asset/encoding.go Show resolved Hide resolved
asset/encoding.go Show resolved Hide resolved
commitment/asset.go Outdated Show resolved Hide resolved
if c.FamilyKey == nil {
_, _ = h.Write(c.ID[:])
} else {
_, _ = h.Write(schnorr.SerializePubKey(&c.FamilyKey.Key))
Copy link
Member

Choose a reason for hiding this comment

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

Thinking about this a bit more, I think we should also apply an invocation of sha2 at this level, to retain the property that an individual can't 100% predict which location in the tree an entry will end up in. Otherwise it may be feasible for someone to attempt to target an x-coord that collides with a given assetID. By forcing things into the same domain, we're able to sidestep those concerns (and also still allow for NUMs points to be used here as well).

Safe to do this in another PR, as we'll need to update the BIP as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Went through them all and updated them in this PR. I'll keep this in mind for the other in-flight PRs, but we'll likely want to do a final sweep anyway.

var rootSum [8]byte
binary.BigEndian.PutUint64(rootSum[:], c.TreeRoot.NodeSum())
leafParts := [][]byte{
{byte(c.Version)}, TaroMarker[:], rootHash[:], rootSum[:],
Copy link
Member

Choose a reason for hiding this comment

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

Though the market byte also already enables this type of verification.

commitment/taro.go Show resolved Hide resolved
commitment/mint.go Show resolved Hide resolved
commitment/taro.go Show resolved Hide resolved
commitment/commitment_test.go Outdated Show resolved Hide resolved
A recent commit introduced a change to create new assets with a genesis
witness by default, which consists of a non-nil PrevID of all zeros.
`PrevID` contains a `btcec.PublicKey`, which is not a valid point when
its X and Y coordinates are both zero, leading to a failure upon
deserialization.

Now, upon deserializing, we'll inspect the bytes to be read and skip
parsing the `btcec.PublicKey` when it's all zeros.
We expose a new function to derive an asset's FamilyKey based on some
internal private key and the asset's genesis.
assetFamilyKey := assets[0].FamilyKey
assetsMap := make(map[[32]byte]*asset.Asset, len(assets))
for _, asset := range assets {
if assetFamilyKey != asset.FamilyKey {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thinking this should also verify the sig is valid for the original asset genesis, though from this point it would be unclear which genesis was the one used to generate that sig.

Copy link
Member

Choose a reason for hiding this comment

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

Can instead be added a later step during sanity check before inclusion in an actual output?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Seems like it should go here since we're already doing similar types of validation. Perhaps worth opening an issue so it doesn't go unnoticed.

Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

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

LGTM 🥙

}

// NewAssetCommitment constructs a new commitment for the given assets capable
// of computing merkle proofs. All assets provided should be related, i.e.,
Copy link
Member

Choose a reason for hiding this comment

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

I think we might eventually want to lift some of these restrictions to give better access to batched asset creation. Ultimately it should also be possible to just do this individually for the relevant set of assets, then merged them all into a single tree afterwards.

I'd need to circle back on some BIP stuff though, right now it's possible if you use a new output for each batch (since we use the output index to derive the asset ID). Alternatively, we'd just want to enforce some unique asset meta for each fo them, which also enables batching

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Batching is still possible as is, you'd just instantiate multiple AssetCommitments for each set of related assets, and merge them together into a single Taro commitment tree with NewTaroCommitment. You are correct though in that with just the existing Mint function as is this is not possible.

assetFamilyKey := assets[0].FamilyKey
assetsMap := make(map[[32]byte]*asset.Asset, len(assets))
for _, asset := range assets {
if assetFamilyKey != asset.FamilyKey {
Copy link
Member

Choose a reason for hiding this comment

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

Can instead be added a later step during sanity check before inclusion in an actual output?

Copy link
Collaborator

@jharveyb jharveyb left a comment

Choose a reason for hiding this comment

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

LGTM, just some nits.

commitment/asset.go Outdated Show resolved Hide resolved
commitment/taro.go Show resolved Hide resolved
commitment/taro.go Show resolved Hide resolved

// Amount is the amount of assets that should be minted for `ScriptKey`.
// NOTE: This should be nil when minting `Collectible` assets.
Amount *uint64
Copy link
Collaborator

Choose a reason for hiding this comment

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

In the BIP for the VM, we use an output value / MS-SMT sum value of 1 in the virtual transaction for verifying transfers of Collectibles.

My understanding is that default won't conflict with the value here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct, that is happening at a different layer. If you follow mintAssets, you can can see how nil gets mapped to 1 unit per collectible asset.

commitment/commitment_test.go Outdated Show resolved Hide resolved
AssetCommitment represents the inner MS-SMT within the Taro protocol
committing to a set of assets under the same ID/family. Assets within
this tree, which are leaves represented as the serialized asset TLV
payload, are keyed by their `asset_script_key`.

TaroCommitment represents the outer MS-SMT within the Taro protocol
committing to a set of asset commitments. Asset commitments, which are
leaves represented as `asset_version || asset_tree_root || asset_sum`,
are keyed by their `asset_family_key` or `asset_id` otherwise.
@Roasbeef Roasbeef merged commit 2020c11 into main May 12, 2022
@wpaulino wpaulino deleted the commitment-mint branch May 13, 2022 00:07
Roasbeef pushed a commit to Roasbeef/taro that referenced this pull request Apr 6, 2023
…known

rpc: turn UNKNOWN batch state into err, move mint related RPCs into mintrpc package
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants