-
Notifications
You must be signed in to change notification settings - Fork 102
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
Conversation
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. |
a7efc73
to
19b1621
Compare
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. |
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). |
var rootSum [8]byte | ||
binary.BigEndian.PutUint64(rootSum[:], c.TreeRoot.NodeSum()) | ||
leafParts := [][]byte{ | ||
{byte(c.Version)}, TaroMarker[:], rootHash[:], rootSum[:], |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
7a21d88
to
7847330
Compare
@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. |
@wpaulino, remember to re-request review from reviewers when ready |
Has some linter failures w/ CI. |
This now depends on #29. |
There was a problem hiding this 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.
commitment/asset.go
Outdated
if c.FamilyKey == nil { | ||
_, _ = h.Write(c.ID[:]) | ||
} else { | ||
_, _ = h.Write(schnorr.SerializePubKey(&c.FamilyKey.Key)) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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[:], |
There was a problem hiding this comment.
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.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
54063a7
to
afaa6b0
Compare
There was a problem hiding this 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., |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 AssetCommitment
s 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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this 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.
|
||
// Amount is the amount of assets that should be minted for `ScriptKey`. | ||
// NOTE: This should be nil when minting `Collectible` assets. | ||
Amount *uint64 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
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.
…known rpc: turn UNKNOWN batch state into err, move mint related RPCs into mintrpc package
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 theirasset_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 asasset_version || asset_tree_root || asset_sum
, are keyed by theirasset_family_key
orasset_id
otherwise.