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.

Many notes include an
ownerwho 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
tokenthis is commenly the note and how it is used:Where the key in the
balancesmap is is the same as theownerof 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
ownerand then change the provider of secrets to a different address.Let us do a bit of renaming because
ownerandownernot being the same make my 👀 bleed.In most usual cases, we would have that
owneris the actor that knows thenullifier_master_secret_keyfor thenullifier_master_public_keyand we just have logic similar to today.In cases of escrowing, you could specify the
owneras the escrow contract, but thenullifier_master_public_keyas 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
ownerbut your key is still thenullifier_master_public_keyfor the note. In this case, you can provide the secrets, but the logic of theownercontract specifies how they are spent.Example 2:
For some betting escrow, the
nullifier_master_public_keycould 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 theownerescrow 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->safeTransferFromincoming).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.
