Validating ownership of a smartcontract utxo, without relying on trusting datum entry

UPDATE: So far this is looking feasible and multifunctional. I created a FAQ at the github repo where I’m going to try to address challenges and answer some common questions I’ve received on reddit: CATs-CardanoAccessTokens/FAQ.md at main · MadeWithLovelace/CATs-CardanoAccessTokens · GitHub

I’m still amateur with haskell and plutus, so maybe I’m wrong about this technically working as I believe it would, so at this point it’s conceptual and I’ll be working to prove the concept the next days.

Imagine this problem: Alice locks funds at a script for a DEX and wants to cancel the order. The smartcontract checks that her pubkeyhash was saved in her datum upon locking, matching the unlock transaction signer with the datum-stored pubkey. The limitation/weakness/vulnerability in this approach means you must trust that the datum was entered as you expected at the time of transaction being built. Datum could be deliberately altered on the back end of a webapp, putting a different pubkeyhash into the datum.

To solve this in a way where we lean nearly entirely on the smartcontract validator, even if using a webapp where we don’t want to be forced to entirely trust the datum entry not being altered, we have to use something that is both available to the smartcontract and irrefutably provable. To be visible it must be in this unlocking transaction script context…like a utxo, a txin or txout, etc. We can also know who signed this unlock from the script context, which is being used to match against datum in the other example.

The solution/concept/proposal I’m working on is “CATs” or Cardano Access Tokens.

To validate this in a way that does not rely on datum having been entered as you expected, and to do so very transparently and irrefutably, Cardano Access Tokens would be a custom token you mint from the relevant wallet, via a minting script which enforces that the name of the token is your pubkeyhash and embeds into the token’s metadata useful information, including this token’s policy hash and locking height for other forms of ownership validation.

How this works in a smartcontract, wherein you don’t need to trust the datum, is as follows:

  • Alice includes one of her CATs in the locking transaction (note, I’m calling them CAT as a generalization, the actual token name would match the wallet owners pubkeyhash)
  • When Alice goes to retrieve her utxo from the smartcontract, she signs a tx that includes another of her CATs and spends both CATs to her own wallet. The script validates this by comparing:
    ScriptContext Signedby == name of CAT in tx-in from wallet == name of CAT in tx-in from script
    and that the CATs are being returned to their owner
  • It can additionally compare against an expected-stored datum value of the pubkeyhash if that were useful in some way.

In addition, smartcontracts could be written to not compare agains the signer for various reasons/usecases, but return the CATs to the owner. And on that note, there would be no issue with a non-owner having a CAT, if the smart contract is written to only allow the unlocking TX if the signer has a matching CAT, it wouldn’t validate a non-owner if it shouldn’t. So a CAT is pretty much useless to a non-owner for nefarious gain, rather useful for a known/trusted CAT “sitter”.

Here is a github I started where I’ll be updating, fielding this concept out more, and posting any progress on testing and proof of concept: GitHub - MadeWithLovelace/CATs-CardanoAccessTokens

I’m pretty new to Haskell and Plutus so I may be missing something about coding it in, so as I said I’ll be testing, but curious to hear from more experienced coders who might know.

Update I was unaware of the token name byte limitation of 32. What this means IMO for this idea/proposal is that the implementation does rely on the datum afterall, and in just as effective a way like so → pubkeyhash is split up, 32 bytes goes to the CAT name, the other 24 bytes from the pubkeyhash are stored in the datum, or vise versa (we’d need a standardized/expected format). Then upon validation the smart contract will check that the CAT name + Datum stored pubkey portion, are equal to the transaction signer for this unlock… and that both CATs are matched/from same policy and being spent to the owner. Updated the git: Native Token name length restriction of 32 bytes · Issue #1 · MadeWithLovelace/CATs-CardanoAccessTokens · GitHub
(edited for clarity)

You could just have a cancel password, instead of a token, that validates that password was entered from a contract wallet. This can initiate process of returning funds minus fees and early withdrawal penalties (if any).
You can probably use IOHK example contract of a guessing game and adapt/ add from it validations needed for the contract you are working on. Take a look at IOHK github here:

The solve-for here is: Not fully trusting the datum and improving trust/reliability in the datum by removing motivation of a malicious party to alter it in cases of validating ownership/pubkey. In general, the idea behind all of this is putting more reliance onto the smartcontract validator and less reliance on trusting that datum was entered correctly.

I updated the github to reflect this: I would propose 28bytes of the users pubkey is the first 28 bytes of the user’s CAT token name, leaving 4 bytes in the name for optional additional numeric indicators. This would allow the cat tokens to function more multipurposely when combined with a particular 4digit numeric indicator and a particular value of CAT being transacted. Giving 3 transparent, provable datapoint functions for which the access tokens can serve.

I’m not sure if this qualifies for a CIP? But it does seem if people agree with this approach that some standards would need to be agreed upon, such as: each cat must have a name of 32bytes, the first 28 = the last 28 of the user’s pubkeyhash and the last 4 of the name = numeric allowing for multiple “sets” of cats. For example, your primary CAT would be named: YOURPUBKEYLAST28BYES0000 ← 4 zeros trailing the 28bytes of pubkeyhash. Another standardization would be the metadata of the CAT, what it contains, it’s expected structure, etc. Special use CATs would have different meta structure potentially.

And the standard way of implementing this in a smartcontract would be that the original locking tx would have the leading 28bytes of the owner’s pubkeyhash, so the plutus script can combine it with the catname 28bytes and compare that 56byte hash with the unlocker’s signing pubkey…as well as compare that the CAT in the input from the script and the CAT in the input from the signer of the unlock have matching policy ids. And finally, it should always only validate if both CATs are being returned to their owner in the unlocking tx output.

The exceptions could be made of course, depending on use case, but this would be the boilerplate validation template.