From a3c97e447cd68d9e23ec43a5f67c24cb174b6900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Mon, 27 Oct 2025 23:43:40 +0000 Subject: [PATCH 1/3] initial sketches for intro sections --- .../index.md | 12 +++++ .../private-token-contract.md | 50 +++++++++++++++++++ docs/pages/aztec-nr/overview.md | 28 +++++++---- 3 files changed, 80 insertions(+), 10 deletions(-) diff --git a/docs/pages/aztec-nr/introduction-to-private-smart-contracts/index.md b/docs/pages/aztec-nr/introduction-to-private-smart-contracts/index.md index e69de29..32dfefe 100644 --- a/docs/pages/aztec-nr/introduction-to-private-smart-contracts/index.md +++ b/docs/pages/aztec-nr/introduction-to-private-smart-contracts/index.md @@ -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 \ No newline at end of file diff --git a/docs/pages/aztec-nr/introduction-to-private-smart-contracts/private-token-contract.md b/docs/pages/aztec-nr/introduction-to-private-smart-contracts/private-token-contract.md index 92cce42..64d3551 100644 --- a/docs/pages/aztec-nr/introduction-to-private-smart-contracts/private-token-contract.md +++ b/docs/pages/aztec-nr/introduction-to-private-smart-contracts/private-token-contract.md @@ -1,2 +1,52 @@ # Private Token Contract +aztec contracts have both private and public functions. let's start with the public part, building an equivalent erc20 token: + +#[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>, + } + + // 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 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 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 private balance (use the balance set library to avoid immediately exposing notes and private set) \ No newline at end of file diff --git a/docs/pages/aztec-nr/overview.md b/docs/pages/aztec-nr/overview.md index 6366dc0..fbfe1d7 100644 --- a/docs/pages/aztec-nr/overview.md +++ b/docs/pages/aztec-nr/overview.md @@ -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. \ No newline at end of file +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 +``` \ No newline at end of file From 5145b1536f7852d5facd43c869934c4f9ae7adcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Tue, 28 Oct 2025 20:47:46 +0000 Subject: [PATCH 2/3] update examples --- .../account-contract.md | 1 + .../private-token-contract.md | 46 +++++++++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/docs/pages/aztec-nr/introduction-to-private-smart-contracts/account-contract.md b/docs/pages/aztec-nr/introduction-to-private-smart-contracts/account-contract.md index 90f8a5d..cd9f48c 100644 --- a/docs/pages/aztec-nr/introduction-to-private-smart-contracts/account-contract.md +++ b/docs/pages/aztec-nr/introduction-to-private-smart-contracts/account-contract.md @@ -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.) \ No newline at end of file diff --git a/docs/pages/aztec-nr/introduction-to-private-smart-contracts/private-token-contract.md b/docs/pages/aztec-nr/introduction-to-private-smart-contracts/private-token-contract.md index 64d3551..d02b89d 100644 --- a/docs/pages/aztec-nr/introduction-to-private-smart-contracts/private-token-contract.md +++ b/docs/pages/aztec-nr/introduction-to-private-smart-contracts/private-token-contract.md @@ -33,12 +33,12 @@ contract token { // function transfer(address to, uint128 amount) external #[external(public)] fn transfer(to: address, amount: u128) { - // read current balance + // 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 recipient, but adding + // 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)); @@ -49,4 +49,44 @@ contract token { // meta: consider also writing a balance_of getter } -now add private balance (use the balance set library to avoid immediately exposing notes and private set) \ No newline at end of file +now add the capacity private balance (use the balance set library to avoid immediately exposing notes and private set). + +#[aztec] +contract token { + #[storage] + struct Storage { + balances: Map>, + // 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, 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. \ No newline at end of file From ccba5b17fe96e47276111946a224054c3fa62264 Mon Sep 17 00:00:00 2001 From: Ciara Nightingale Date: Wed, 29 Oct 2025 16:40:35 +0000 Subject: [PATCH 3/3] add missing backticks --- .../private-token-contract.md | 4 ++++ docs/pages/aztec-nr/overview.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/pages/aztec-nr/introduction-to-private-smart-contracts/private-token-contract.md b/docs/pages/aztec-nr/introduction-to-private-smart-contracts/private-token-contract.md index d02b89d..73cfd20 100644 --- a/docs/pages/aztec-nr/introduction-to-private-smart-contracts/private-token-contract.md +++ b/docs/pages/aztec-nr/introduction-to-private-smart-contracts/private-token-contract.md @@ -2,6 +2,7 @@ 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 @@ -48,9 +49,11 @@ contract token { // 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] @@ -82,6 +85,7 @@ contract token { self.emit(to, Transfer { self.msg_sender, to, amount }); } } +``` things to notice that were different: - new state variables with different primitives diff --git a/docs/pages/aztec-nr/overview.md b/docs/pages/aztec-nr/overview.md index fbfe1d7..c28c9f9 100644 --- a/docs/pages/aztec-nr/overview.md +++ b/docs/pages/aztec-nr/overview.md @@ -15,6 +15,6 @@ Make it hard to shoot yourself in the foot by making it clear when something is 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); // 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 ``` \ No newline at end of file