I like the proposed approach by @zac-williamson â especially over creating random single use addresses at the contract level.
If a dApp or wallet developer has to manage multiple addresses, the wallet UX will be poor â unusable. You will have to show your assets split between your public and private accounts, or attempt to aggregate them 
The protocol could have a deterministic way to generate those addresses, or someone (us?) creates a very strong standard, however that seems like a bit of a minefield.
It could be worth removing the message sender all together for private > public transactions. A big move away from Solidity, and I am sure will have other negative consequences but some rational for discusion.
Letâs take two versions of a DEX that letâs a user add liquidity.
DEX Aâ Records positions in a public mapping msg.sender > position
DEX B â Records the pool total in public state, but positions in the private state tree
IMO, end users will pick between two protocols as follows:
- I am using Aztec I have privacy.
- Protocol A is 1 tx, vs protocol B which is 2.
- Pick protocol A.
Some time later⌠all liquidity will be on protocol A as many users pick A.
If some liquidity still exists on B, B users will have a higher cost to using the private version (from multiple txs or slippage).
Dev Ex
The contract developer devex without msg.sender
in the public world is actuably reasonable thanks to Note abstractions.
Note this relies heavily on a custom note implementation that works like AC claim notes.
// ignore the horrific rust / solidity mashup.
import privateToken from './token'
import note from './note'
contract {
// UTXO set mapping for storing private notes
Option(field: Option(field: note::UTXOSet)): privateBalances = [][];
private function privateLP(Field: amount, Field: assetIdTo, Field: assetIdFrom, Field:
privateOwner) {
contract privateToken::at(assetIdTo);
// send this contract a specific note (imagine this exists)
note::UTXO:note1 = privateToken::transferFromNote(
amount);
// contract owns the note, it can nullify
note1::remove();
// create a peddersen encryption of the owner
field privateOwner = note::peddersenEncrypt(msg.sender);
// call a public function
public this::addLP(note1.value, assetIdTo, assetIdFrom, privateOwner)
}
public function addLP(Field: amount, Field: assetIdTo, Field: assetIdFrom, Field:
privateOwner) {
// get the price ( we can do this as we are in the public land)
(to, from) = this::computeAtoB(amount, assetIdTo, assetIdfrom);
// missing checks for slippage
poolTotals[assetIdTo] = poolTotals[assetIdTo] + to;
poolTotals[assetIdFrom] = poolTotals[assetIdFrom] + from;
// always return any state for the user to the data tree, likely using peddesen homomorphic hashing magic.
// this would be decided by the note implementation used
privateBalances[assetIdTo][assetIdFrom]::insert(note::completeEncryption(privateOwner, amount));
}
}
TLDR
UTXOâs are the perfect throwaway addresses IMO.