I think probably the most productive thing here from my side to present an explict “counter proposal”.
So here I will write out the resource logic that I propose. I will change minimal amount of things here, but given the discussion I thought maybe it will be worth writing out explicitly. The core change is that in order to make sure that the user authorizes the external burn, they look up that value.owner contains the appropriate Ethereum address. That was as the user signs over all commitments and nullifiers they control who is the receiver of Ethereum funds.
I will be using the same assumptions as the original post using the dot syntax to signify getting info from some data. M is only used differently in the non-ephemeral case, where it does not include any external payload data. The non-ephemeral consumption currently requires a signature but that is only needed if we want authorized reception, which I am unsure about now as looking at the original post I do not see non-ephemeral creation branch for some reason
This design should achieve what I think we want and not need any changes on the RM side. However let me know if there are any explicit security assumptions we are missing.
UPD: Can M.cmAndNulfs just be the action tree root? Or maybe that is what was originally means and I didn’t get it. In this case we don’t need that part in the payloads and we just take it from the instance.
UPD: After discussion with @cwgoes we don’t need reception authorization so I removed the signature check from non-ephemeral creation.
UPD: Updated the message format to just combine the external payload and tree root
I think it’s good to keep this in mind but I don’t think this should influence any of the technical discussions as of now. Even with 10 Gwei (which, as you noted, is much higher (~40x) than current gas prices), I would argue that $4,000 to deploy 100(!) assets isn’t outrageous. Besides, we have the possibility to set aside ecosystem funds to support this.
I’m assuming that the only action item to take away from this is to explicitly note the requirement of deploying custom forwarder contracts in our docs. Noted, we’ll write a section about this.
Edit: I see this has sparked a productive debate. I very much agree with the decision to punt any forwarder contract structure changes to a future version. Let’s focus on shipping what we’re working with right now.
I was not suggesting to stall any progress. simply noting this reality. if one cannot note these things in this thread happy to post the analysis elsewhere.
Yeah, I shouldn’t phrase it like that. On the logic side we can just do sha256(externalPayloaf.receiverAddress), no need for a notion of an owner here
Assume that we just check sha256(externalPayloaf.receiverAddress). The point is that I thought the original query was “how does the owner authorize burns and control who they are sent to” (i.e. some resources being withdrawable back to Ethereum) and the evident answer is: by signing over the all committments and nullifiers in the action. If the logic controls for this by containing this in e.g. valueRef then signing over such an ephemeral commitment can be interpreted as an authorization for a burn of that resource’s quantity and kind to address which is part of valueRef
There is a notion of an owner though, the owner of a resource is the user who signs the consumption. The owner is represented by their identity public key.
To rephrase: we just use the ephemeral resource to pass externalPayload data to the action and signing over this ephemeral resource commitment indirectly signs over the externalPayload associated with the ephemeral resource. Is that understanding correct?
Right, I mean that on the ephemeral creation side, we don’t need any signatures or whatnot. The ephemeral creation value only serves to corresponds to the external calldata, so that it can be available in the context of other resources in the action.
I assume this is a stable version now. I have a few questions regarding implementation:
Currently, externalPayload contains the address, input, and output. We are using externalPayload.receiverAddress, applicationPayload.S, and externalPayload.senderKey in this design. We should align with the structs to ensure proper decoding by PA. Could someone write it down?
What signature scheme should we use in circuits? I noticed there are two verifications. You also mentioned the permit2. I suppose we can continue using the current ECDSA for non-ephemeral consumption? If we need to switch to permit2 for Mint, could you share details about the signature scheme used in PA? I’ll look for a compatible version in circuits.
I don’t fully understand the constraint r.value.owner = externalPayload.receiverAddress in Burn. The value in ephemeral resources isn’t reliable, so constraining it may be unnecessary unless we explicitly verify it somewhere. Even we do the constraints, the value` and receiverAddress can be tampered without affecting balance or other checks. From a user’s perspective, do they care about the receiver address when burning tokens?
The struct is still the same, but I wrote it down poorly. What we mean is that abi.decode(input) results in the datatype with the appropriate fields.
In the current draft implementation it’s something like
/// @notice A structured call
/// @param ammount Ammount to withdraw
/// @param ERC20Addr The address of a relevant ERC20
/// @param userAddr The address of user
/// @param minting Indicates whether we the call is to mint or to burn.
struct ERC20Call {
uint256 ammount;
address erc20Addr;
address userAddr;
bool minting;
}
Yes, for sure, whatever you think fits best
Here @Michael is the specialist. To be honest, I do not actually think we need to do any in-circuit signature checks for minting. These should be done using the permit2 on the Ethereum side. i.e. if the permit2 signature is bad, then the transfer will not succeed and the whole transaction reverts
Yes, because when you “burn” on the Ethereum land you withdraw actual assets. You care who you send them to.
An evident example is that when I burn 10 USDC Resource it matters whether you or I receive the 10 USDC on the Ethereum side.
Could you elaborate on what’s included in address, input, and output and how they are encoded? I can’t decide based on my preference since PA also needs to decode them.
If PA checks the signature and handles the failure, I can skip signature verification in the circuits. I still need to find a Permit2 library in Rust to generate the applicationPayload for testing. So pls share the Permit2 you’re using in PA once it’s finalized
Got it. However, in Burn, we cannot guarantee that r.value.owner is associated with the owner of the consumed persistent resource. For example, if there are consumed resources in Action1, a malicious actor could create an ephemeral resource in Action2 to balance and burn the resources from Action1. Unless we assume that the consumed resources and the balancing ephemeral resources for burning are always created together in a single action by the same actor, or there is no unbalanced actions.
address, input, and output types should be the same as they are currently. That is, address is an address type, input is bytes and output is just bytes.
output should be some indication of the success of the operation, that we can just agree on.
input is a bit more tricky. abi.decode(inputs) should contain at least the stuff that is in the ERC20Call I outlined above, yet it should also contain the Permit2 signature and I am unsure whether there need to be extra data connected to it.
For the burn, it should be just ERC20Call as above, i.e. abi.decode(inputs, (ERC20Call)) should succeed.
Yes, that is actually absolutely fine! We should support burning to arbitrary Ethereum addresses as they are not internally connected to Anoma identities either way. I should burn to an arbitrary account, as long as I approve of it, i.e. when I non-ephemerally consume, I sign over all the resource in the action, including those, containing the information of the Ethereum address I send to.
Does that make sense?
If we want some e.g. solver to instead do the burning to some arbitrary resource, we sign over an unbalanced Action.
Yes, that’s fine with me if you can accept those assumptions. I just wanted to emphasize that the issue and burn must be completed in a single action in this design; otherwise, malicious actors could “help” you issue or burn the remaining amount. And if there are any unbalanced actions that are not finalized to a balance tx, not just for burning, it can be maliciously balanced/burned.
With the ERC-1167: Minimal Proxy Contract pattern, we can have a factory that can provide forwarder clones and also maintain control over the implementation to guarantee safety.
Adding a new ERC-20 token would be something that could happen through a frontend (potentially after a allowlist update by us).
I would not worry about deployment cost. Runtime costs (mostly coming from risc0 proof verification) is what we have to reduce.
For a contract, e.g., the Uniswap DEX, to be able to pull ERC-20 tokens from a user’s balance, the user must approve this address and set an allowance.
This requires an additional transaction to be sent by the user previous to the intended transaction (e.g., to a DEX like Uniswap) that will call ERC-20 transferFrom to pull the funds.
Permit2 eliminates this extra transaction by allowing the user to send a signature along the call that authorizes the contract, e.g., the DEX, to pull a certain amount (up to a certain deadline).
The ERC-20 forwarder that we are building will pass this signature to the permitTransferFrom in the Uniswap’s immutable Permit2 contract, which acts as a trusted intermediary and facilitates the transfer.
The Permit2 signature is over a specific digest and allows you to add custom witness information to it. Furthermore, the signature is protected by an unordered nonce
As discussed with @ArtemG and @xuyang today, we must bind it to the action (or carrier resource) to protect it against usage in a different context.
Otherwise, this signature might be used by an adversary, e.g., in forwarder contracts supporting multiple kinds.
We agreed to discuss this in a dedicated meeting about ther forwarder design during the HHH.
I agree that this is possible and has clear advantages (ease of deployment, deployment costs). Besides marginally increase runtime costs, there is the disadvantage that multiple tokens are hold and different resource kinds interact with the same contract, which increases the attack surface.
An alternative would be the forwarder factory based on the minimal proxy pattern that I briefly touched on in my response to @apriori.
I am already slightly leaning towards the multi-ERC20 design as well, but we should explore the attack vectors carefully.