Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# Account Contract

(i don't think this is a good example, it doesn't illustrate a lot and is too complicated. a simple game might be better, to provide a non-token example, but i can't think of a good simple example. something like battleship would be good but that'd probably require semaphores and arrays (or building those manually), so that's not great.)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
note:
we assume you already know what a smart contract is and are familiar with solidity. if you do not not, head to https://docs.soliditylang.org/en/v0.8.30/introduction-to-smart-contracts.html.
we also assume you have basic knowledge of what a zero knowledge proof is and what noir looks like - if you do not, head to ???? (noir docs)?

A Solidity smart contract on Ethereum has `external` functions anyone can call, but everything about executing them is public information to the entire internet: when they were called, who called them, and with what parameters. additionally, all contract state is also public: eg anyone can query the token balance of any account at any time. sharing an ethereum address with someone means also sharing all past and future onchain activity with them forever. aztec extends smart contracts by adding privacy to them.

an aztec contract has public external functions, and these work just like in ethereum. but it also has _private_ external functions, which don't reveal anything: the caller, all parameters, even the fact that the contract was executed, these all remain hidden. aztec contracts can also have _private_ state, which is stored encrypted onchain so that only account with the corresponding keys can read it. but despite none of this information being revealed, private functions can still be verified for correct execution via zero knowledge proofs.

note:
in solidity 'public' and 'private' specify function visibility, along with 'external' and 'internal' (https://docs.soliditylang.org/en/latest/contracts.html#function-visibility). in aztec, the terms 'public' and 'private' **always** refer to whether information is public (i.e. known to all) or private (i.e. known to some) - never to function visibility. aztec contracts do have both 'external' and 'internal' functions however, and their meaning is the exact same as in solidity (i.e. whether a function can be called from the outside or not).

private smart contracts are quite different from regular smart contracts, because building private applications is hard. there are many considerations to be had when building such an application, and the design and tradeoff space is very large. aztecnr provides multiple utilities to make this task easier
Original file line number Diff line number Diff line change
@@ -1,2 +1,96 @@
# Private Token Contract

aztec contracts have both private and public functions. let's start with the public part, building an equivalent erc20 token:

```rust
#[aztec]
contract token {
// declare state variables
#[storage]
struct Storage {
// use map to create a publicmutable state variable for each user. a public mutable value is one that is publicly
// known (not private), and which can be mutated (changed) - other kinds of state variables exist. this public
// mutable holds u128 (unsigned 128 bit integers), for the token balance of each account
//
// the equivalent solidity declaration is
// mapping(address => uint128) private balances;
balances: Map<Address, PublicMutable<u128>>,
}

// declare an event for token transfers
//
// the equivalent solidity declaration is
// event Transfer(address from, address to, uint128 amount);
#[event]
struct Transfer {
from: Address,
to: Address,
amount: u128,
}

// declare an external public function for transferring tokens.
//
// the equivalent solidity declaration is
// function transfer(address to, uint128 amount) external
#[external(public)]
fn transfer(to: address, amount: u128) {
// read the sender's current balance
let current_sender_balance = self.storage.balances.at(self.msg_sender).read();
// subtract amount and store again
self.storage.balances.at(self.msg_sender).write(current_sender_balance.sub(amoun));

// same for the recipient, but adding
let current_recipient_balance = self.storage.balances.at(to).read();
self.storage.balances.at(to).write(current_recipient_balance.add(amoun));

// emit an event
self.emit(Transfer { self.msg_sender, to, amount });
}

// meta: consider also writing a balance_of getter
}
```

now add the capacity private balance (use the balance set library to avoid immediately exposing notes and private set).

```rust
#[aztec]
contract token {
#[storage]
struct Storage {
balances: Map<Address, PublicMutable<u128>>,
// use map to create a balance set state variable for each user. whenever a user is sent tokens private, a note
// is created and added to this set, and the aggregate of all notes makes up their balance.
private_balances: Map<AztecAddress, BalanceSet<Context>, Context>,
}

// declare an external private function for transferring tokens. the called function, parameters, contract address,
// caller, will all remain hidden
#[external(private)]
fn transfer_privately(to: address, amount: u128) {
// subtract from the sender's set
let change_note_message = self.storage.private_balances.at(self.msg_sender()).sub(amount);
// this likely created a 'change' note if the sender's notes did not exactly add up to the amount to send. a
// message with the contents of this note is sent to the sender so that they can find it and use it
change_note_message.deliver(self.msg_sender());

// same for the recipient, but adding
let recipient_note_message = self.storage.balances.at(to).add(amount);
// the recipient also needs to be told about the the note created for them - otherwise they won't be able to
// find it, as it is private
recipient_note_message.deliver(to);

// emit an event - also privately! in this case we deliver the encrypted message with the event contents to the
// recipient, to let them know we've transferred to them
self.emit(to, Transfer { self.msg_sender, to, amount });
}
}
```

things to notice that were different:
- new state variables with different primitives
- because state changes are private, we need to send messages to the parties involved so that they can find the relevant secret information

this example glosses over a lot of details, including the different ways there are to deliver messages, the performance tradeoffs that exist in private functions, or even how to authorize other contracts to spend tokens.

something else that's not covered here because it greatly exceeds the scope of a simple demonstration is how to _combine_ private and public functions. it's not just public state with private functions bolted on - the two can interact with one another. for example, it is possible to privately transfer tokens to someone and have them show up as their public balance, or to take public tokens and transfer them to a private recipient. it is even possible to set things up so that a third party can send us tokens in the future _without knowing who we are_, for example completing a private order in an orderbook-based exchange.
28 changes: 18 additions & 10 deletions docs/pages/aztec-nr/overview.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
# Aztec.nr Overview

## Mike's notes

1. Intro
1. Noir vs Aztecnr ←- move it somewhere
2. Justify [Aztec.nr](http://Aztec.nr) - why do we need it?
3. Design principles
- (Example):
- Make it hard to shoot yourself in the foot.
- Make it clear when something is unsafe.
- Rails that intentionally trigger a developer's "WTF?" response, to ensure they understand what they're doing.
aztec.nr is a noir library used to develop and test aztec smart contracts. it contains both high level abstractions (state variables, messages) and low level protocol primitives, providing granular control to developers if they want custom contracts.

## why is it needed

noir can be used to write circuits, but aztec contracts are more complex than this. they include multiple external functions, each of a different type: circuits for private functions, avm bytecode for public functions, and brillig bytecode for utility functions. the circuits for private functions are also quite particular in that they need to interact with the protocol's kernel circuits in very specific ways, so manually writing them and then combining everything into a contract artifact is quite involved work. aztec-nr takes care of all of this heavy lifting and makes writing contracts as simple as marking functions with the corresponding attributes e.g. `#[external(private)]`.

it also allows safe and easy implementation of a bunch of well understood design patterns, such as the multiple kinds of private state variables, making it so developers don't need to understand the nitty gritty of how the protocol works. these features are optional however, and advanced users are not prevented by the library from building their own custom solutions.

## design principles

Make it hard to shoot yourself in the foot by making it clear when something is unsafe. Dangerous actions should be easy to spot. e.g. ignoring return values or calling functions with the `_unsafe` prefix. This is achieved by having rails that intentionally trigger a developer's "WTF?" response, to ensure they understand what they're doing.

a good example of this is writing to private state variables. These functions return a `NoteMessagePendingDelivery` struct, which results in a compiler error unless used. This is because writing to private state also requires sending an encrypted message with the new state to the people that need to access it - otherwise, because it is private, they will not even know the state changed.

```
storage.votes.insert(new_vote); // compiler error - unused NoteMessagePendingDelivery return value
storage.votes.insert(new_vote).deliver(vote_counter); // the vote counter account will now be notified of the new vote
```