# Broadcasting notes in token contracts

**URL:** <https://forum.aztec.network/t/broadcasting-notes-in-token-contracts/2658>\
**Category:** Aztec\
**Created:** [December 18, 2023, 6:30pm UTC](https://forum.aztec.network/t/broadcasting-notes-in-token-contracts/2658 "2023-12-18T18:30:11Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![spalladino](https://dub1.discourse-cdn.com/flex013/user_avatar/forum.aztec.network/spalladino/32/32_2.png) [@spalladino](https://forum.aztec.network/u/spalladino)\
**Post date:** [December 18, 2023, 6:30pm UTC](https://forum.aztec.network/t/broadcasting-notes-in-token-contracts/2658/1 "2023-12-18T18:30:11Z")

</div>

_Given the different ways we have for sharing notes (provable on-chain, on-chain, and off-chain), the different types of encryption keys we envision (incoming, outgoing, internal incoming), and the different use cases (direct transfer, swap, etc), what should a token contract do in the event of a transfer?_

## Provable encryption and broadcasting

When creating a note, we have three different options for encrypting and sharing it:

1. Create the note without constraining its encryption, and then share the encrypted note with the recipient via an offchain channel.

2. Encrypt the note without constraining it, but share it onchain by broadcasting an event.

3. Provably encrypt and tag the note, and broadcast it onchain.

## Encryption keys and a transfer example

We envision three different flavors of encryption keys. From the yellow paper, by @Mike:

> Definitions (from the point of view of a user (“yourself”)):
> 
> - Incoming data: Data which has been created by someone else, and sent to yourself.
> - Outgoing data: Data which has been sent to somebody else, from you.
> - Internal Incoming data: Data which has been created by you, and has been sent to yourself.
> - Note: this was an important observation by ZCash. Before this distinction, whenever a ‘change’ note was being created, it was being broadcast as incoming data, but that allowed a 3rd party who was only meant to have been granted access to view “incoming” data (and not “outgoing” data), was also able to learn that an “outgoing” transaction had taken place (including information about the notes which were spent). The addition of “internal incoming” keys enables a user to keep interactions with themselves private and separate from interactions with others.

Let’s apply these keys in a token transfer example:

- Sender consumes one or more notes that sum up to at least the amount to transfer
- A note is created for the recipient with the amount transferred, and is encrypted with their **incoming viewing key**
- That same note is also encrypted for the sender themselves, using their **outgoing viewing key**
- A change note is created for the sender and encrypted with their **internal incoming viewing key**

So, what should we do in terms of provable encryption and broadcasting in each scenario…?

## Encrypting for self

When encrypting for self, the outgoing and incoming internal scenarios, there is **no need to constrain correct encryption**. But whether to broadcast the note on chain depends exclusively on whether the user wants to be able to recover their state or transaction history from on-chain data. This is a tradeoff between tx cost and recoverability, and may depend on each user, each app, and even the value of the note.

The ~~easiest way out for us~~ most flexible option is to **let the wallet software decide**. The software could decide for the user based on their preferences, or even let them choose on a note-by-note basis if they are advanced enough.

Implementation-wise, this means that encrypting and tagging with an incoming internal or outgoing key should not be constrained, and broadcasting should depend on the result of an oracle call.

## Encrypting for someone else

Let’s now go into the note sent to the recipient. As we discussed in the very beginning of this post, there are valid scenarios for provable encryption, for onchain broadcast, and for cheap offchain broadcast. And these depend on the context of the transfer, not on the token implementation itself.

- If the `transfer` is being executed by another app, like a swap contract, it’ll need to constrain encryption and broadcasting, to prevent a malicious sender from griefing the recipient.
- If the `transfer` is direct from one user to another, we most likely don’t need to constrain encryption and tagging, but it’s unclear whether to broadcast it on-chain:
  - If the sender wants to broadcast in order to avoid manually sending the note off-chain, all good.
  - But if the receiver wants the note broadcasted so they can reconstruct their state later, they are putting that burden on the sender, who needs to pay for the extra gas cost. Also, we don’t have the receiver’s wallet software around to aks it whether the receiver wants to have this specific note broadcasted or not (as we did in the “encrypt for self” scenario).

From the above it seems that we need to add a flag to the `transfer` method (or have two different flavors of `transfer`) such that the msg.sender can choose whether to provably encrypt or not.

As for broadcasting, it seems we have a few options:

1. We can have the receiver record their preference of “I want my noted broadcasted” in the registry, but this does not provide flexibility per-app (much less per-note).
2. We can make it so the sender _always_ broadcasts in a token transfer, for the sake of simplicity and security, at the expense of more expensive transfers.
3. We can add a way for the receiver to send a tx themselves where they broadcast the note they received off-chain - if they so much want the onchain backup, have them pay for it themselves (though the extra cost of an additional tx probably makes the whole thing more expensive overall).

## Encrypting for sender by someone else

We should stress that “encrypting for self” and “encrypting for the sender” is not the same. Thanks to authwits and [randomized nullifiers](https://forum.aztec.network/t/how-to-handle-private-escrows-between-two-parties/2440/2), an authorized third party could transfer funds on behalf of the sender, usually in the context of a broader tx. Think of a `transferFrom` in Ethereum executed from a contract, like a swap.

These `transferFrom` operations should follow the same rules as when “encrypting for someone else” (described above), since what matters is who is locally executing the tx then and not the owner of the funds. Still, here we have the option of the sender encoding their broadcasting preferences in the authwit message.

Note that, if the incoming viewing key is used here, this will lead to records that describe value sent will be sometimes encrypted with an outgoing key (when the executor of the tx was the owner) and sometimes with an incoming key (when the executor was an authorized third party). We could avoid this issue by always using the outgoing key, regardless of who encrypts, but this means we cannot use the outgoing secret key for encryption as originally planned.

## Encryption and compliance

Note that any of the scenarios described above where encryption is unconstrained or broadcasting is optional potentially break audit compliance for the app. If an application requires all its interactions to be auditable by having the user share an app-siloed key with a 3rd party auditor, then unconstrained encryption or optional broadcasting can be used for bypassing it. Apps that have audit compliance requirements should always provably encrypt and broadcast, even if it’s more expensive.

## Wrapping up

We have the following decisions to make:

- Encrypting data for self should be conditional on an oracle call, so the wallet controls whether to generate an outgoing trail or backup change notes.
- Token `transfer` method should accept a flag for whether to prove encryption of the recipient note or not.
- Token `transfer` method should always broadcast the recipient note, for the sake of simplicity.
- Outgoing encryption can only be done by the holder of the key, so even for notes that “semantically” mean outgoing data (eg `transferFrom`), if they are produced by a 3rd party, we use the incoming key.

* * *

> “_The information set out herein is for discussion purposes only and does not represent any binding indication or commitment by Aztec Labs and its employees to take any action whatsoever, including relating to the structure and/or any potential operation of the Aztec protocol or the protocol roadmap. In particular: (i) nothing in these posts is intended to create any contractual or other form of legal relationship with Aztec Labs or third parties who engage with such posts (including, without limitation, by submitting a proposal or responding to posts), (ii) by engaging with any post, the relevant persons are consenting to Aztec Labs’ use and publication of such engagement and related information on an open-source basis (and agree that Aztec Labs will not treat such engagement and related information as confidential), and (iii) Aztec Labs is not under any duty to consider any or all engagements, and that consideration of such engagements and any decision to award grants or other rewards for any such engagement is entirely at Aztec Labs’ sole discretion. Please do not rely on any information on this forum for any purpose - the development, release, and timing of any products, features or functionality remains subject to change and is currently entirely hypothetical. Nothing on this forum should be treated as an offer to sell any security or any other asset by Aztec Labs or its affiliates, and you should not rely on any forum posts or content for advice of any kind, including legal, investment, financial, tax or other professional advice.”_

---

<div class="post-metadata">

**Author:** ![Mike](https://dub1.discourse-cdn.com/flex013/user_avatar/forum.aztec.network/mike/32/7_2.png) [@Mike](https://forum.aztec.network/u/Mike)\
**Post date:** [December 18, 2023, 9:29pm UTC](https://forum.aztec.network/t/broadcasting-notes-in-token-contracts/2658/2 "2023-12-18T21:29:24Z")

</div>

Do we need to make opinionated decisions here, or can we just let app/wallet devs decide? Are you seeking to create an opinionated token standard?

> [@spalladino](#):
>
> When encrypting for self, the outgoing and incoming internal scenarios, there is **no need to constrain correct encryption**

I might disagree with this one. I think there might be a need, depending on how the app might wish to build in auditability functionality. Proving who you sent funds to feels like a useful primitive, in some cases. Or perhaps an app wants over-protect users from losing their funds, so it forces correct encryption.

> [@spalladino](#):
>
> And these [scenarios for when to constrain encryption to someone else] depend on the context of the transfer, not on the token implementation itself.

I can envisage a token contract implementation which wishes to enforce correct encryption?

> [@spalladino](#):
>
> Also, we don’t have the receiver’s wallet software around to aks it whether the receiver wants to have this specific note broadcasted or not (as we did in the “encrypt for self” scenario).

We can call their account contract though - could that be a useful way to get this preference?

> [@spalladino](#):
>
> Note that, if the incoming viewing key is used here, this will lead to records that describe value sent will be sometimes encrypted with an outgoing key (when the executor of the tx was the owner) and sometimes with an incoming key (when the executor was an authorized third party). We could avoid this issue by always using the outgoing key, regardless of who encrypts, but this means we cannot use the outgoing secret key for encryption as originally planned.

I haven’t quite followed this paragraph, sorry.  
Are we saying that:

- If a user wants to spend their own funds, they use keys:
  - tx auth secret key to sign the tx request.
  - nullififer secret key to generate a nullifier for which no-one else knows the preimage
  - outgoing secret key to generate an outgoing ciphertext, potentially describing the note which was spent and/or new notes which have been created by the user.

- If a 3rd party is given permission to spend a user’s funds, the user cannot give the 3rd party their secret keys; they can only give the 3rd party a signature representing “tx auth”, but they can’t easily provide the ability to nullify nor the ability to encrypt outgoing messages (because secrets need to stay secret)?

> [@spalladino](#):
>
> 1. Encrypting data for self should be conditional on an oracle call, so the wallet controls whether to generate an outgoing trail or backup change notes.
> 2. Token `transfer` method should accept a flag for whether to prove encryption of the recipient note or not.
> 3. Token `transfer` method should always broadcast the recipient note, for the sake of simplicity.
> 4. Outgoing encryption can only be done by the holder of the key, so even for notes that “semantically” mean outgoing data (eg `transferFrom`), if they are produced by a 3rd party, we use the incoming key.

1. The app might have a strong preference to generate an outgoing trail and to backup change notes. Imo, either: the app should have the ability to detect that an account contract has denied an encryption request and then the app might choose to revert; or the user’s account contract shouldn’t be able to refuse such a call from an app.
2. Doesn’t this depend on the requirements of the token contract?
3. Doesn’t this depend on the requirements of the token contract?
4. Perhaps we need to more-exactly define “outgoing” data in this context. I’d been understanding it to mean “data that I (a user) have created, which is primarily intended for someone else’s future consumption”. With that definition, I suppose I view the scenario of a 3rd party spending Alice’s notes as “incoming data”, from Alice’s perspective.

* * *

Separately, it would be nice if we could distill all our imagined token scenarios into a very minimal comparison, to help us to see how to design a single token contract standard which can enable all scenarios without too much ugly conditional logic:

- Me spending my notes
- A 3rd party (without my secrets) spending my notes
- An escrow smart contract spending its notes
  - And all the variants, relating to how many people can initiate spending of escrowed notes; whether all possible “spenders” need to be known in advance; etc etc.

---

<div class="post-metadata">

**Author:** ![spalladino](https://dub1.discourse-cdn.com/flex013/user_avatar/forum.aztec.network/spalladino/32/32_2.png) [@spalladino](https://forum.aztec.network/u/spalladino)\
**Post date:** [December 19, 2023, 1:06pm UTC](https://forum.aztec.network/t/broadcasting-notes-in-token-contracts/2658/3 "2023-12-19T13:06:50Z")

</div>

Thanks as always for your replies, Mike!

> [@Mike](#):
>
> Do we need to make opinionated decisions here, or can we just let app/wallet devs decide? Are you seeking to create an opinionated token standard?

I’m trying to identify what a vanilla token implementation would look like, to test if the decisions we’ve made on keys make sense. So yeah, an opinionated token standard would be the result of this exercise.

> [@Mike](#):
>
> I think there might be a need, depending on how the app might wish to build in auditability functionality. Proving who you sent funds to feels like a useful primitive, in some cases.

> [@Mike](#):
>
> I can envisage a token contract implementation which wishes to enforce correct encryption?

Agree, I noted this as I was finishing the post, hence the section on “compliance”.

> [@Mike](#):
>
> We can call their account contract though - could that be a useful way to get this preference?

Good point. If we’re not constraining, we can safely call an unconstrained function to get the result, and can just ignore it if it fails.

> [@Mike](#):
>
> I haven’t quite followed this paragraph, sorry.

Sorry, it ended up quite confusing. My point is that the outgoing key is meant to encrypt “outgoing” data, so we use the outgoing _secret_ key for encryption. However, there are situations where a 3rd party sends outgoing data on behalf of the user, such as in a `transferFrom`. And since we cannot give the 3rd party the outgoing secret key (because secrets need to stay secret, as you say), the 3rd party would have to encrypt this data with the user _incoming_ public key, or we’d have to design for an outgoing _public_ key.

Nullifiers are a different story: we know from the [escrow use case](https://forum.aztec.network/t/how-to-handle-private-escrows-between-two-parties/2440/2) that, in order to allow a 3rd party to spend a user’s notes, those notes need to be nullified with a random secret rather than requiring the owner’s nullifier secret key. So that’s “solved”.

> [@Mike](#):
>
> - The app might have a strong preference to generate an outgoing trail and to backup change notes. Imo, either: the app should have the ability to detect that an account contract has denied an encryption request and then the app might choose to revert; or the user’s account contract shouldn’t be able to refuse such a call from an app.
> - Doesn’t this depend on the requirements of the token contract?
> - Doesn’t this depend on the requirements of the token contract?

Agree, in all these scenarios the app (token in this case) may have a preference one way or another. Maybe the problem here is that we’re talking about a very general token contract, so requirements are too flexible. Let’s assume we’re working with a utility token, that has no auditability or compliance requirements, which I think would be the most “vanilla” scenario.

> [@Mike](#):
>
> 1. Perhaps we need to more-exactly define “outgoing” data in this context. I’d been understanding it to mean “data that I (a user) have created, which is primarily intended for someone else’s future consumption”. With that definition, I suppose I view the scenario of a 3rd party spending Alice’s notes as “incoming data”, from Alice’s perspective.

Excellent, I agree, and that makes things easier. The downside of this is that “your tokens you’ve sent” and “your tokens someone else has sent on your behalf” are encrypted using different keys, but that’s not too bad.

> [@Mike](#):
>
> Separately, it would be nice if we could distill all our imagined token scenarios into a very minimal comparison, to help us to see how to design a single token contract standard which can enable all scenarios without too much ugly conditional logic:

Sounds good, will tackle this during the week!

---

<div class="post-metadata">

**Author:** ![spalladino](https://dub1.discourse-cdn.com/flex013/user_avatar/forum.aztec.network/spalladino/32/32_2.png) [@spalladino](https://forum.aztec.network/u/spalladino)\
**Post date:** [December 22, 2023, 8:15pm UTC](https://forum.aztec.network/t/broadcasting-notes-in-token-contracts/2658/4 "2023-12-22T20:15:27Z")

</div>

> [@Mike](#):
>
> Separately, it would be nice if we could distill all our imagined token scenarios into a very minimal comparison, to help us to see how to design a single token contract standard which can enable all scenarios without too much ugly conditional logic:
> 
> - Me spending my notes
> - A 3rd party (without my secrets) spending my notes
> - An escrow smart contract spending its notes
> - And all the variants, relating to how many people can initiate spending of escrowed notes; whether all possible “spenders” need to be known in advance; etc etc.

Last working day of the year, but I finally got to this! Much of the work has already been done by @LHerskind [here](https://hackmd.io/3-UbzocrRVOcqZIBiNubDQ?view), but let’s recap all scenarios and their requirements. For each of them, we need to identify who’s the “application owner” of the note, who’s the “protocol owner” (as in who can nullify), and who can “see” the note (as in who has the preimage of it).

All scenarios begin with Alice having a note `A` in the token contract that represents part of her balance.

### Alice sends a transfer to Bob

- Alice calls `token.transfer(Bob)`
- Token consumes note `A` such that Alice is both its app and protocol owner using her nullifier key
- Token creates note `B` such that Bob is both app and protocol owner
- Token broadcasts the note to Bob, and both Alice and Bob can “see” it

### Alice deposits into a public contract

- Alice calls `defi.deposit()`
- The defi contract calls `token.unshield_from(Alice, this)`
- Token contract calls into Alice’s contract to check for authorization
- Alice provides her authwit via an oracle call
- Token consumes note `A` such that Alice is both its app and protocol owner using her nullifier key
- The defi contract’s public balance is incremented

### Bob transfers Alice’s funds to Charlie

This scenario requires notes with shareable nullifiers, as opposed to using nullifier secret keys. The way it has been implemented so far is by using a randomized value as a nullifier (see [here](https://forum.aztec.network/t/how-to-handle-private-escrows-between-two-parties/2440/2)), which gets stored in the note, but **this requires a different note format**.

- Alice converts note `A` into `A'` **such that it uses a random value stored in the note as nullifier** via a `token.escrow()` call
- Alice signs an authwit to authorize the transfer and sends it to Bob off-chain along with note `A'`'s preimage which includes the randomized nullifier.
- Bob calls `token.transfer_from(Alice, Bob)`
- Token contract calls into Alice’s contract to check for authorization
- Bob provides Alice’s authwit via an oracle call
- Token consumes note `A'` using the random value stored in the note preimage
- Token creates a note `C` for Charlie, such that Charlie is both the app and protocol owner
- Token broadcasts note `C` to Charlie

Would it be possible, instead of using a random value as nullifier, to have Alice derive the specific nullifier for the note and share it with Bob, and for Bob to be able to verify it without knowing Alice’s secret nullifier key? For instance, instead of deriving the nullifier as `nullifier = hash(note_hash, nullifier_secret_key)`, could we derive the nullifier as `shareable_nullifier = sign(private_note_hash, nullifier_secret_key)`, such that we can then verify it against the `nullifier_public_key`? Note the usage of a `private_note_hash` here which should be different from the `note_hash` stored in the private data tree, otherwise anyone could link the note and its nullifier.

### Bob deposits Alice’s funds in a public contract

Equivalent to a mix of scenarios 2 and 3:

- Alice converts note `A` into `A'` **such that it uses a random value stored in the note as nullifier** via a `token.escrow()` call
- Alice signs an authwit to authorize the deposit and sends it to Bob off-chain along with note `A'`'s preimage
- Bob calls `defi.deposit()`
- The defi contract calls `token.unshield_from(Alice, this)`
- Token contract calls into Alice’s contract to check for authorization
- Bob provides Alice’s authwit via an oracle call
- Token consumes note `A'` using the randomized value stored in the note
- The defi contract’s public balance is incremented

### Alice deposits her funds into a contract 0xB which then transfers publicly into 0xC

This is Lasse’s [transient escrowing](https://hackmd.io/3-UbzocrRVOcqZIBiNubDQ?both#Transient-Escrowing) scenario, in which contract 0xB performs some accounting before forwarding the funds to 0xC. Requires adding a `protocol_owner` option to the transfer method, or using a random value as nullifier.

- Alice calls `0xA.deposit()`
- Contract `0xA` calls `token.transfer_from(Alice, this, protocol_owner: Alice)`
- Token contract calls into Alice’s contract to check for authorization
- Alice provides her authwit via an oracle call
- Token consumes note `A` (such that Alice is both its app and protocol owner) using Alice’s nullifier key
- Token creates note `B` such that 0xB is the app owner but she is the protocol owner
- Contract `0xA` calls `0xB.deposit()`
- Contract `0xB` calls `token.unshield_from(0xA, this)`
- Token contract calls into 0xA to check for authorization
- `0xA` approves via custom logic (not via authwit, since there is no key to sign it)
- Contract `0xB` consumes note `B` **using Alice’s provided nullifier**
- The public balance for `0xB` is increased

### Alice deposits her funds privately into a betting contract with Bob and Charlie

This is [Wonderland’s escrow scenario](https://forum.aztec.network/t/how-to-handle-private-escrows-between-two-parties/2440/3), where we have a handful of parties that will be working together privately through a contract, and eventually one of them will cash out. Here, Alice, Bob, and Charlie need to provably see all escrowed notes, and be able to nullify them.

- Alice calls `bet.deposit()`
- Betting contract calls `token.transfer_from(Alice, this, nullifier: random, broadcast_for: [Alice, Bob, Charlie])`
- Alice consumes note `A` (such that Alice is both its app and protocol owner) using her nullifier key
- Token creates note `B` such that the betting contract is the app owner and **the nullifier is a random value** stored in the note itself.
- Token **broadcasts note `B` to Alice, Bob, and Charlie**.

Eventually the bet is settled. Let’s assume Charlie wins:

- Charlie calls `bet.cashout()`
- Betting contract calls `token.transfer(Charlie)`
- Token consumes note `B` using the random value stored in the note
- Token creates note `C` where Charlie is the app and protocol owner

Note that, in the scenario that David wants to join in the bet contract after it started, we’d need someone to re-broadcast every note (or a single note created from merging all individual notes) to him. This means that we’d need a `token.broadcast_for` method, as Wonderland implemented [here](https://github.com/defi-wonderland/aztec-token/blob/12cd9853076f86e053d63ae44a6bd73fb67b176a/src/contracts/src/main.nr#L422-L454).

This scenario could also be implemented by creating a new contract specifically for Alice, Bob, and Charlie (assuming they are all known in advance), using a set of public encryption keys derived from a shared secret, so they just treat the bet contract as another “account contract” they own. The downside of this approach is note discovery: all parties need to start scanning notes for yet another address, and they all need to agree in a single note tagging and encryption method.

* * *

I may be missing other scenarios, please comment and I’ll edit the post!
