nucypher icon indicating copy to clipboard operation
nucypher copied to clipboard

Finalize updatable conditions design

Open cygnusv opened this issue 2 years ago • 13 comments

Proposal 1: Smart contracts approach

The high-level idea is that conditions are either static, as we have now, or updatable, in which case the requester will provide a new condition file together with the request and the node will validate such conditions on a smart contract. In more detail:

  • Static conditions: The format is the one that we're already using. If a ciphertext is created in static conditions mode, conditions are immutable, since the ciphertext is cryptographically tied to a specific condition file.
  • Updatable conditions: During encryption, ciphertexts are bound to an "asset category ID" (an integer/bytes32) and a "condition reference contract (CRC), following the format of an Updatable Conditions Metadata (UCM) file. When evaluating conditions for a requested ciphertext, the node will obtain a conditions file hash (CFH) from the CRC based on the asset category ID. The requester must obtain the actual conditions file that matches the CFH from a side-channel (e.g., IPFS); optionally, the requester can get it from a conditions provider contract (CPC), which can potentially be implemented on the CRC. Either way, now the node has both ciphertext and conditions file, and can proceed with condition evaluation as usual.

Specification

Updatable Condition Metadata (UCM)

{
    "assetCategory": "foobar",
    "conditionsReferenceContractAddress": "0xabcdef",
    "conditionsReferenceContractChain": "1"
}  

Conditions Reference Contract Interface

interface IConditionsReference {
    function getConditionHash(bytes32 assetCategoryID) external view returns (bytes32);
}

interface IConditionsProvider {
    function getConditionsData(bytes32 assetCategoryID) external view returns (bytes);
}

Questions and comments

  • What's the semantics of asset category IDs? How are they assigned/produced? The problem of updating conditions can be solved by adding a level of indirection between ciphertexts and conditions; in other words, instead of immutably binding a ciphertext to a condition, we map it to an intermediate concept (the "asset category"), which we can later link to different conditions. This still doesn't answer the question of what is an "asset category", but IMHO it's OK it's a bit ambiguous and it should be up to the application/adopter/creator how to interpret it (although I'm aware this is somewhat kicking the can down the road).
  • Note that a smart contract that implements the Conditions Provider interface doesn't necessarily have to store the conditions. Depending on how it is designed, it can also produce conditions data dynamically based on on-chain state, similarly to how some NFTs allow for some updateability/dynamic content.
  • NuCypher should provide a simple implementation and deployment of a Conditions Reference Contract, so adopters don't have to code and deploy their own (but of course they can do it if they want to). A simple approach here would be to allow everyone to submit condition hashes for asset categories, but only the original submitter can update them. Open to ideas here for more complex update logic.
  • The smart contract approach allows to easily charge a service fee for updating conditions.

cygnusv avatar Feb 06 '23 18:02 cygnusv

Nice thinking here @cygnusv .

The smart contract approach allows to easily charge a service fee for updating conditions.

This is interesting. Perhaps this point was just a musing. but let's dig in.

Does this mean that by deploying their own contract, adopters can avoid any smart contract fee (other than gas) for updating conditions?

This feels like a quick race to an eventual 0-fee model. Someone deploys the same contract with lower fees, then someone else does the same with even lower fees, and eventually, someone deploys the contract with 0 fees and/or adopters routinely deploy their own and avoid any fee anyway.

If that's the case, then it might necessitate a 0-fee contract anyway, and it really used to reduce adopter friction, and not as a revenue-generating service. Would there be some liability here for a faulty contract if it is our deployed contract vs the adopter deploying their own...(thinking about Bqeth's use case here)?

Would any such a contract likely have be non-ownable and non-upgradeable?

derekpierre avatar Feb 06 '23 18:02 derekpierre

Perhaps this point was just a musing.

You got me, I didn't think this through. We can either close the system (i.e., not allowing user-provided CRCs) and enable to charge fees, or open the model and basically forgo the fees, as you very well identified.

Would there be some liability here for a faulty contract if it is our deployed contract vs the adopter deploying their own...(thinking about Bqeth's use case here)?

Unsure, but this contract can have a very simple implementation to reduce potential attack vectors.

Would any such a contract likely have be non-ownable and non-upgradeable?

Yeah, I think so, but again depends on the open vs closed model question. If it's open adopters can do what they want (i.e. it could be ownable and upgradeable, assuming that's a good idea...), but if it's a closed model and Threshold DAO or NuCypher deploy the contract, then I would definitely make it non-ownable and non-upgradeable.

cygnusv avatar Feb 06 '23 20:02 cygnusv

Iterating a bit over the previous design. Main changes:

  • The ciphertext hash must be passed to the IConditionsReference.getConditionsHash() method, which enables to update conditions on per-ciphertext basis.
  • Added a list of attributes instead of a single identifier to serve as a reference to the conditions. This allows more expressiveness to adopters
{
    "attributes":
        [
            ...
        ],
    "conditionsReferenceContractAddress": "0xabcdef",
    "conditionsReferenceContractChain": "1"
}  
interface IConditionsReference {
    function getConditionsHash(bytes32[] attributes, bytes32 ciphertextHash) external view returns (bytes32);
}

interface IConditionsProvider {
    function getConditionsData(bytes32[] attributes, bytes32 ciphertextHash) external view returns (bytes);
}

This should allow for both default and exceptional conditions. E.g., Adopter uses CBD to gate access to assets A, B and C, with, in principal, the same set of conditions (with hash value X). Hence, their metadata file should look initially the same, and for all of them the call to getConditionsHash() should return hash X. However, at some point, conditions of asset C should be changed to a new set of conditions, with hash value Y. This can be changed individually in the IConditionsReference contract, so a call to getConditionsHash() for asset C should return value Y.

cygnusv avatar Mar 20 '23 15:03 cygnusv

I tried to follow, that seems reasonable. I have no desire to implement a CRC, which would need to be non-ownable as well for us anyway also. I'm happy with having to interact with the CRC to switch out the condition for a fee. BTW, switching out to a new condition that never evaluates to true, effectively implements a pause mechanism, or even revocation.

jdbertron avatar May 08 '23 02:05 jdbertron

Outstanding questions (@cygnusv @derekpierre):

  • How is the authority to update a given conditionset rolled out to an end-user (i.e. wallet-level granularity)
    • Potentially create master CRC contract, then revoke control for certain asset categories in favor of adopting application, who in turn revoke their control of certain asset categories for end-users. Similar to trusted set-up approach followed immediately by trustless control
  • What is the relationship between the condition updater and those with decryption rights? e.g. you can buy three flavors of private NFT, (i) where you also get condition update privileges, and therefore can rent access, & (ii) where someone else retains the update privilege, so you're effectively renting access trustfully, and (iii) where the conditions are immutable, so as long as you hold the corresponding NFT, you will have access perpetually, or can transfer decryption rights – trustless access but less power than (i)
  • How can we make condition updates cheaper wrt gas? e.g. presigned conditions
  • Are condition updates (or indeed, creation) a chargeable payment-gate?
    • Cohort checks if the condition hash is in a registry of paid hashes before provisioning decryption material
    • Need more business cases for condition updates (renting assets, wallet hack, reconfiguring business model)
    • Free-riding question: in which domains is it functional/rational to find existing payers who have both (a) very freely circulating NFTs and (b) very generic conditions – versus paying minimal infra costs and gaining the power to customize conditionality, plus not risk some third party unpredictably updating the conditions and disrupting service for end-users

arjunhassard avatar May 08 '23 14:05 arjunhassard

Food for thought:

  • If we decide conditions should not updateable, then the last say, always, about what's the current condition associated to a given ciphertext is decided at encryption time. Hence, an off-chain condition file cryptographically associated to the ciphertext is the way to go.
  • If we decide conditions should be updatable, then we suddenly need consensus on what's the current condition associated to the ciphertext. Hence the need for some on-chain logic that provides such consensus (e.g., a hash of the condition file as proposed above).
    • However, I see a somewhat unnecessary complexity here. We would be introducing required on-chain interactions to update the consensus on the incumbent conditions, specifically to integrate some off-chain data (the condition file). At this point, wouldn't be better to simply codify conditions in a smart contract to begin with? Essentially, the design rationale would be: if you want permanent conditions, use a condition file; if you want updateable conditions, use a condition smart contract.
    • The only problem I see with this approach, and where the approach I proposed initially still works, is that off-chain condition files would allow for multi-chain conditions (e.g., ciphertext is decrypted if Bob satisfies condition A in Ethereum and condition B in Polygon). A smart contract approach for conditions would be, in the majority of cases, limited to a single chain. The question now is if the need for multi-chain conditions exists and how relevant it is.

cygnusv avatar May 16 '23 15:05 cygnusv

Just to add that for the condition smart contract approach, we could provide all the necessary functionalities to make it easy and intuitive for users, including:

  • Predefined conditions contracts (e.g., ERC20/721 owner, ETH balance, blocknumber/timestamp constraints, etc)
  • Factory contracts
  • Conditions composer tools to create complex conditions (e.g. like Furucombo for Defi actions)

cygnusv avatar May 16 '23 15:05 cygnusv

@KPrasch asserted a few days ago that we already have support for updatable conditions, and I think this is partially true. A condition set could be as simple as a call to a custom contract function with :userAddress as parameter, where this contract can implement whatever access control logic, updatable or not (with the caveat they can only be conditions for the contract chain and some RPC methods can't be accessed, like querying TX data). In fact, this approach completely bypasses any control from Ursula's side.

Knowing this, an open question is if this is an acceptable design updatable conditions, or do we have a more opinionated view on how updatable conditions should work.

cygnusv avatar May 22 '23 14:05 cygnusv

This is an acceptable design for the narrower question of 'how are conditions updated'. The framework that's missing would answer 'who is allowed to update conditions?', 'how do they they gain (/lose this right?' and 'how is this authenticated?'. These questions exist in the wider context of https://github.com/nucypher/nucypher/issues/3082#issuecomment-1553345052

the caveat they can only be conditions for the contract chain and some RPC methods can't be accessed, like querying TX data

given the above, solving this category of limitation (along with making updatable conditions chain-agnostic) would be a premature optimization

arjunhassard avatar May 22 '23 16:05 arjunhassard

The framework that's missing would answer 'who is allowed to update conditions?', 'how do they they gain (/lose this right?' and 'how is this authenticated?'.

What I was trying to raise with my comment before is: should we even have an opinion for the these questions? It doesn't make sense for arbitrary call conditions (as mentioned above), so I think only for the case of closed condition sets we could consider being opinionated about them and proposing enforcement mechanisms.

cygnusv avatar May 22 '23 17:05 cygnusv

Are we in agreement to move this out of 7.0.0, and the current workaround is for adopters to write their own contract and use a ContractCondition call.

Can we move this out of 7.0.0 to 8.0.0? (cc @cygnusv , @arjunhassard )

derekpierre avatar Jun 14 '23 14:06 derekpierre

is this done?

KPrasch avatar Jun 14 '23 14:06 KPrasch

I've moved this to v8.0.0 for now. There is useful thinking contained in this issue that will inform the OpenDRM working group, the contract work for which will likely end up as an independent project. Designing and maintaining a condition update authority structure is one of four 'services' that can may be bundled into an offering to media adopters (the other three being payment management, cohort management, and storage + streaming + metatadata standardization). Note that this comment is also a useful starting point for this design: https://github.com/nucypher/nucypher/issues/3082#issuecomment-1553345052

arjunhassard avatar Jun 14 '23 16:06 arjunhassard