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

feat(Keys_Nullifier): nullify an escrowed note #5864

Closed
Tracked by #5606
LHerskind opened this issue Apr 19, 2024 · 5 comments
Closed
Tracked by #5606

feat(Keys_Nullifier): nullify an escrowed note #5864

LHerskind opened this issue Apr 19, 2024 · 5 comments
Assignees

Comments

@LHerskind
Copy link
Contributor

LHerskind commented Apr 19, 2024

Many notes include an owner who is the provider of nullification secrets. In most of the implementations, this is the same entity that the logic of the contract sees as the owner.

Example, for the token this is commenly the note and how it is used:

#[aztec(note)]
struct TokenNote {
    // the amount of tokens in the note
    amount: U128,
    // the provider of secrets for the nullifier. The owner (recipient) to ensure that the note 
    // can be privately spent. When nullifier secret and encryption private key is same 
    // we can simply use the owner for this one.
    owner: AztecAddress,
    // randomness of the note to hide contents.
    randomness: Field,
}

// mapping(AztecAddress owner, Privateset<TokenNote> notes) balances;
balances: Map<AztecAddress, PrivateSet<TokenNote>>

Where the key in the balances map is is the same as the owner of the notes in the that it is mapping to.

As have been discussed at other occaisions, one way to escrow a note, is to use the logic to guard the owner and then change the provider of secrets to a different address.

Let us do a bit of renaming because owner and owner not being the same make my 👀 bleed.

#[aztec(note)]
struct TokenNote {
    amount: U128,
    nullifier_master_public_key: Point,
    randomness: Field,
}

// mapping(AztecAddress owner, Privateset<TokenNote> notes) balances;
balances: Map<AztecAddress, PrivateSet<TokenNote>>

In most usual cases, we would have that owner is the actor that knows the nullifier_master_secret_key for the nullifier_master_public_key and we just have logic similar to today.

In cases of escrowing, you could specify the owner as the escrow contract, but the nullifier_master_public_key as another. Which to chose would depend on the duration. For a "transient escrow" the ownership could simply be the actor sending the funds.

Example 1:
You use some contract to aggregate actions, so you transfer funds into it where it is not the owner but your key is still the nullifier_master_public_key for the note. In this case, you can provide the secrets, but the logic of the owner contract specifies how they are spent.

Example 2:
For some betting escrow, the nullifier_master_public_key could be a shared key between the betting participants, allowing any of them to create the nullifiers and the notes are then constrained only by the logic of the owner escrow contract.

Issue!

While this sounds all good and dandy for transfers that are complex. We need to provide additional inputs to the function. This could be done as having a separate function that is used for just handling this kind of escrowing where the key is also inputted - or by using an oracle.

An issue with the oracle is that other contracts cannot easily constrain what you are feeding in there (Would need to have you prove the membership and that the real key is in there, a lot of annoying logic on top of a transferFrom -> safeTransferFrom incoming).
E.g., for the examples above, I could use the oracle to always just feed in my own address even if I am sending funds to someone else or depositing into a contract.

If doing this, it behave similarly to having sent funds, but they will not be spendable by the recipient.

When dealing only with person-to-person transfers that is like never sending, and people would look angry at you and not give you what you paid for.

When dealing with contracts that handle funds it gets worse. As you at the time of execution could seem genuine and if not checked sufficiently the funds looks like they are in the "balance" of the recipient, but they cannot be spent, so if it gives you are receipt token you practically have that the receipt is unbacked.
This could lead to you exiting with other peoples funds and them not being able to exit at the end even though the funds are there if they are not spendable.

Is the funds really yours if you cannot spend them?

The freedom brings a new issue - unless it is provided as an argument, how do you know the correct "nullifier_master_public_key`.


Note that the diagram is talking about secrets as well, but I see this as the same in practice on how it influences the usage as needing something passed in that somehow must be constrained.
image

@rahul-kothari
Copy link
Contributor

Note that there is another edge case - "open bets"/open oracles where I want to enter into a bet or want someone to fulfill my request at a price but I do not know who that specific person will be - it is open to the public to see who wants to challenge me or fulfill my request.

@LHerskind
Copy link
Contributor Author

Beyond the issues of figuring out who should be nullifiying, it might also be quite strange on who should be the "sender" of outgoing logs.

It gets especially weird if the contract always assumes that someone is emitting outgoing. Say that I send funds to Rahul, who don't have an outgoing specified, or it might be a contract that don't or whatever. The if it is a requirement that the outgoing is emitted it would not be possible to transfer from these accounts.

Essentially making stuff like the "sent funds to contract, are they lost" worse as there could be functions to "rescue" funds, but that they are not possible to get out because the out was never specified - only some funds can be rescued then.

@LHerskind
Copy link
Contributor Author

LHerskind commented Apr 24, 2024

Note that there is another edge case - "open bets"/open oracles where I want to enter into a bet or want someone to fulfill my request at a price but I do not know who that specific person will be - it is open to the public to see who wants to challenge me or fulfill my request.

You could just use a "known key" for that. Meaning that anyone know the information to nullify, but the logic of the application must be satisfied first.


The current best model I can really think of is to have the extra function, which can be used to specify the owner and nullifier_master_public_key separately. The function would mostly be used by apps I think, e.g., similarly to transferFrom in ERC20 it could use it to pull the funds and set the keys to something controlled in the application logic, e.g., the user himself for transient or some shared key for the betting or maybe a publicly known key for anyone to pick it up.

A danger would be that if used to send funds to wallets etc, it would seem to them as they have notes that might increase their balances or the like, but then not actually being able to spend them (as mentioned earlier).

The spend-ability is defined by the individual contract, but assuming not too many oddities in most of the tokens it should be covered by being able to compute the nullifier which is also currently done as part of the unconstrained compute_note_hash_and_nullifier that the PXE is using when adding notes to its database.

A benefit is that the model doesn't really need anything new from Aztec.nr for the nullification.

For the other kinds of keys, we might again run into some "oddities", which I will briefly discuss here for some kind of completeness:

  1. For notes where the sender IS also the actor with nullifier_master_public_key, the outgoing logs are a waste of DA, as the actor knows the content just from the incoming already.
  2. For notes that are not matching 1) and the recipient IS NOT the actor with nullifier_master_public_key, we need to also get a hold of the address related to nullifier_master_public_key as we might not "know" later who we sent the thing to ("who as the address here") -> need extra inputs.
  3. For notes that are transient the logs generally (incoming and outgoing) is a waste of DA, and the should i) never even be created or ii) be pruned as part of note squashing.
  4. A contract not specifying an incoming key will not be able to receive tokens that MUST log the incoming log, even though it could have received the data separately, and nothing in would stop it from spending the funds.
  5. A contract not specifying an outgoing key might not be able to spend certain notes even if it can receive them and correctly nullify them. This case would not be caught when adding the note, and need you to try and spend to know you could
  6. If the most up to data value MUST always be used for the nullifier_master_public_key and we do not allow using a zero value, contracts could potentially "brick" systems by making transfers to them revert. In the world of ethereum, this is sometimes called king of the hill. Similar issue for the incoming_master_public_key unless zero would just mean don't try to broadcast.
  7. If we allow zero values as mentioned in 6), we do not allow contracts to nicely reject transfers of useless tokens to them, e.g., not getting rid of the classic "i sent funds to the contract" issue in this manner.
  8. A contract that allows users to specify what kinds of log they wish to broadcast would run into the same issue of oracles as mentioned in the issue itself or break the interface unless it already take it into account. Interfaces could get really odd.

@LHerskind
Copy link
Contributor Author

LHerskind commented May 2, 2024

While people disliked my naming, the below should describe with enough flexibility to support the escrowing.

Image

The implementation will be easier if waiting for #5630 to be addressed, as it is then mostly changing a few input values, and reusing some of the logical changes in there.

@LHerskind
Copy link
Contributor Author

Will be closing this along with #6632.

@LHerskind LHerskind closed this as not planned Won't fix, can't repro, duplicate, stale Jun 11, 2024
@github-project-automation github-project-automation bot moved this from Todo to Done in A3 Jun 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

No branches or pull requests

3 participants