Skip to main content

Partial Notes

There are a few instances where the value of a user's note cannot be known in private. Partial notes are a way to transfer public to private without revealing the recipient.

To do this, partial notes have a cryptographic commitment to the owner and other data, without a specification of its value. This way, a public function can complete them by setting the value in public, and emitting the hash corresponding to a normal note.

In order to preserve the total supply, a partial note's completion in public is necessarily accompanied by decrementing the public balances of the sender by an equivalent amount.

Motivation example: AMMs

There are many instances where the value of a note isn't known in privateland. For instance, Aztec's reference AMM is based in Uniswap V2's constant product formula.

In order for the AMM to work without outside intervention, the pool's balances for token0 and token1 are public, and its balance movements after a user's swap will therefore also be public.

Now, consider an exact-in swap. The input token can be transferred private to public, but the output amount depends on things that may happen in public.

Instead of doing a normal public to private transfer, which would reveal the user's address, the AMM prepares a partial note for the output token in private. Once the value is known in publicland, it needs only complete the partial note with the output amount.

Mechanism

In order for this to work, the complete note's hash must be a function of the commitment to the data and the note's value. In the previous section, you've seen the default implementation of a note's hash by the #[note] macro. To allow for partial note's mechanism, UintNote uses the #[custom_note] macro instead, which allows for the definition of a custom note hash.

The hash is then computed as follows:

let commitment = poseidon2_hash_with_separator(
[owner, randomness, storage_slot],
GENERATOR_INDEX__NOTE_HASH,
);
let hash = poseidon2_hash_with_separator([commitment, value] GENERATOR_INDEX__NOTE_HASH);

The commitment is computed in private and stored in a PartialUintNote that can then be made public.

Bonus: previous mechanism

Partial notes used to have a different hash. While it was changed for performance reasons, its homomorphic property with respect to addition makes it worth mentioning. The complete hash was computed as follows:

let hash = owner * g_owner + storage_slot * g_slot + randomness * g_randomness + value * g_value

where the g variables are distinct generators of the elliptic curve.

By taking a note's hash and adding delta_value * g_value, you'd construct the hash for a note with value + delta_value.