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

Marlowe contracts lack on-chain identity #223

Closed
edsko opened this issue Aug 11, 2022 · 7 comments
Closed

Marlowe contracts lack on-chain identity #223

edsko opened this issue Aug 11, 2022 · 7 comments
Assignees
Labels
bug Something isn't working

Comments

@edsko
Copy link
Contributor

edsko commented Aug 11, 2022

Proviso: I've labelled this as a bug after a fairly thorough deep-dive into the code, but it's possible that I have misunderstood something. In that case, please accept my apologies 🙂

Most contracts that are state-machine like abstractions make use of an NFT as a unique identifier of any particular instance of the contract. In earlier implementations of Marlowe, where it still used the Plutus state machine abstraction machinery, this was the case as well because the SM machinery provided it implicitly. However, the current implementation does not use the state machine abstraction anymore, and no NFT is created for new Marlowe contract instances.

When the Marlowe off-chain code tries to find the UTxO that holds the current state of the contract in getMarloweTxOuts, it does this purely on the basis of the SmallTypedValidator. This in turn depends only on the MarloweParams:

data MarloweParams = MarloweParams {
        rolePayoutValidatorHash :: ValidatorHash,
        rolesCurrency           :: CurrencySymbol
    }

Here rolePayoutValidatorHash is normally set to mkRolePayoutValidatorHash rolesCurrency, so the only thing that varies here is the rolesCurrency.

When a particular Marlowe contract does not use roles, that rolesCurrency is set to adaSymbol. This means that the search for a UTxO for a Marlowe contract without roles will fail with an AmbiguousOnChainState error if there is any other Marlowe contract running at all. Worse, if the contract we think we are interacting with has actually terminated, we might instead be interacting with the wrong contract (if there happens to be a single other one).

This makes the use of roles almost compulsory. When a new contract is started and it uses roles, a new role currency symbol is created using OneShotCurrency. Since this is anchored to a particular UTxO, just like an NFT is, this will be unique every time a new contract is created.

This improves the situation: now every contract that is created using the Marlowe off-chain code does in fact have a unique ID, due to that unique rolesCurrency. However, as far as I can tell, it does not solve the problem: nothing is preventing malicious users from creating another UTxO at the same validator with the same MarloweParams. This is not the intended usage, of course, but nothing in the on-chain code prevents this from happening. As before, the off-chain code will detect the problem and throw an AmbiguousOnChainState error when it happens -- but again, it will fail to detect the problem if the original contract has terminated, leaving the possibility we might be able to trick people in engaging with the new contract.

We could perhaps make the situation slightly better by using a role payout script that instead requires that role tokens are handed in (like it does now) and are then burned when funds are redeemed. This means that if funds are redeemed, the original contract cannot be interacted with anymore. But that's still an "if"; under some circumstances it might be worthwhile for an attacker to trick people in locking up funds even if they (attacker or victim) cannot redeem them. It doesn't feel like a real solution.

I think the solution here is is to create an NFT (state token) for each new contract instance, and add that NFT to MarloweParams. This way we can now uniquely pointed to an instance of a Marlowe contract on-chain, and there is no ambiguity. When the contract terminates and the payouts are made, the state token would be burned so the contract cannot be interacted with anymore and it's impossible to start another contract with the same NFT.

It would then probably be good if the role payout script hash would also depend on this NFT, so that this is tied down as well.

@edsko edsko added the bug Something isn't working label Aug 11, 2022
@bwbush
Copy link
Collaborator

bwbush commented Aug 29, 2022

@edsko, thank you for your helpful analyses and suggestions.

I think the solution here is is to create an NFT (state token) for each new contract instance, and add that NFT to MarloweParams.

This is the idea solution, and a future version of Marlowe's Cardano implementation may thread identity tokens through each contract. (One of the recent Marlowe Improvement Proposal discussions called this a validatorToken: marlowe-lang/MIPs#1 (comment).) This approach makes is much less awkward to prevent double-satisfaction attacks.


This means that the search for a UTxO for a Marlowe contract without roles will fail with an AmbiguousOnChainState error if there is any other Marlowe contract running at all.

Yes, the PAB-based backend was only designed to handle the contracts that do use role tokens. That code is being replace by a Marlowe Runtime that remedies the difficulty with discovering contract history for role-less Marlowe contracts: https://github.com/input-output-hk/marlowe-cardano/tree/cardano-node-1.35/marlowe-runtime.


We're currently changing the application and payout validators to have constant hashes (one script per Marlowe version, regardless of role currency symbol) in order to take advantage of CIP-33 reference scripts. This requires several changes to maintain on-chain security and off-chain contract-history discovery: https://github.com/input-output-hk/marlowe-cardano/tree/SCP-4367-constant-validator.

Finally, we've had quite a bit of discussion about integrating the monetary policy with the spending validator and have an implementation of that. There is a tradeoff between flexibility in monetary policy vs tighter control of minting/burning, and the approach interacts with things like constancy or non-constancy of addresses.

@edsko
Copy link
Contributor Author

edsko commented Sep 12, 2022

Hi @bwbush,

Thank you for your detailed response, and apologies for my slow reaction, it's been a busy two weeks. I have studied the MIP you linked to, read through (most of) the discusison, and also took a look at the proof-of-concept implementation that is linked to from the MIP (#117).

Let me try to summarize the proposal, to make sure I understand it correctly:

  1. Before we start, we pick a TxOut that will become the identity of the contract.
  2. The role token minting policy is parameterized by this TxOut, tying roles to a specific contract instance: different TxOut, different role token minting policy hash.
  3. To create the roles, we create a transaction that must spend the chosen TxOut; it creates the various role tokens (specified through the redeemer for the role token minting policy). It must also start the contract in its initial state, thereby tying the verification of the initial state to the verification of the role creation. (Tying in nicely with my latest blog post on Verifying initial conditions in Plutus :-))

A consequence of this is that the role token minting policy is no longer a parameter, but a fixed part of the Marlowe ecosystem (whether or not this is the "universal validator" that #117 proposes I think is orthogonal).

This all sounds great and will work just fine for our use case, provided that we can still choose our role payout script. This is the only way for us to hook into the execution of a Marlowe script, and that ability is critical for us. But nothing in the MIP or #117 suggests that that will change, so I guess that is fine. We will need to change the off-chain code I suppose, but that is already the case: the evaluator takes the role payout script as an argument but the off-chain code doesn't currently allow to choose; which is fine; we can use our own off-chain code. For us it will be critical that the role payout script is also aware of that TxOut, but that is also unproblematic, I think: we can make the TxOut a parameter to our custom role payout script, and then set the MarloweParams accordingly; at least, the MarloweParams in #117 are

data MarloweParams = MarloweParams {
        rolePayoutValidatorHash :: ValidatorHash,
        uniqueTxOutRef          :: (TxId, Integer)
    }

which doesn't preclude the possibility that the rolePayoutValidatorHash has also been computed based on the (TxId, Integer) txout.

@edsko
Copy link
Contributor Author

edsko commented Sep 12, 2022

Oh no, I just realized that that branch you linked to removes the option to set a different role payout validator... :-o That would be a disaster for us, as that is the only way that we effect the execution of the contract. Why is that being removed...?

@edsko
Copy link
Contributor Author

edsko commented Sep 12, 2022

Ok, I have read @paluh 's proposal a bit more carefully again. The proposal to mint one special "role" that will act as the validator token is indeed exactly in line with what I was proposing above.

The proposal also states that the role payout script will not be a parameter anymore; but it doesn't say (unless I missed it somewhere) how role payouts will then work instead. Will the funds remain locked in the contract UTxO, and they can be redeemed one by one from there? I think that would work for us actually; I need to think a bit more about it. The good thing about that is that we can then verify that that special "validator token" is also present in that UTxO.

This is the thing that I wanted to discuss in relation to my original proposal, above: even if we introduce a validator token for the contract, we would still lose the connection between the execution of that contract and the various payouts, and that link is important for us. We need to know that this payout came from the execution of this script; that identity is key. I think with @paluh 's proposal, if I understand correctly that the funds are not paid out to different scripts at all, that problem disappears. The cost to it I guess is that funds must be redeemed sequentially, but that doesn't seem like a huge deal.

@edsko
Copy link
Contributor Author

edsko commented Sep 12, 2022

@bwbush @paluh I realize I've been rambling a bit above. I would like to try and summarize what I think you guys are proposing and are implementing, to make sure I understand it correctly.

  1. An arbitrary TxOut Id is chosen to serve as the identity of the contract. This Id will be known to all participants (must be communicated out of band).
  2. Before the contract starts, all the necessary role tokens are minted, as well as one distinguished role token known as the "validator token", which will serve as the state token for the contract.
  3. Id is a parameter of the role token minting policy (different Id, different role token CurrencySymbol).
  4. The minting policy for the role tokens verifies that the transaction that mints the role tokens spends Id, so that these role tokens (and, crucially, the validator token) cannot be created again.
  5. When the contract terminates, all funds stay locked in the contract until they are redeemed. There are no separate outputs created for each role: there is only a single output. Moreover, that one output will also contain the validator token.
  6. Funds can be redeemed from the contract if the contract is closed, but redemption will still "step" the contract state (the funds for that role are removed from the contract state). When redeeming, we check that the validator token is present and that the appropriate role token is presented.

Importantly (for us), point (5) means that when redeeming, we can check for the presence of the validator token, and so know for sure that the funds that are being redeemed really came from the execution of the contract with identity Id.

Could you let me know if the above understanding is correct? (If it is, that would perfectly for us, and we do not need the ability to specify a different role payout script after all.)

@paluh paluh self-assigned this Dec 15, 2023
@paluh
Copy link
Member

paluh commented Mar 12, 2024

Hi @edsko,

We are actually planning to introduce proper thread token on the validator level in the next version of the validator: #714

I'm closing this for now and thanks a lot for all your input Edsko!

@paluh paluh closed this as completed Mar 12, 2024
@edsko
Copy link
Contributor Author

edsko commented Mar 12, 2024

Thanks for letting me know! And apologies for disappearing off the radar, the client for which I was doing this work paused the work, hence I've been busy with other things instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants